package com.tbit.uqbike.client.service.impl;


import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.NumberUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.Lists;
import com.tbit.common.config.TbitProperties;
import com.tbit.common.utils.TbitHttpServiceUtil;
import com.tbit.uqbike.center.pojo.MachineStatus;
import com.tbit.uqbike.client.constant.*;
import com.tbit.uqbike.client.constant.enums.TbitApiParkErrorCode;
import com.tbit.uqbike.client.dao.core.*;
import com.tbit.uqbike.client.dao.log.OrderReturnDao;
import com.tbit.uqbike.client.dao.log.PhotoUploadLogDao;
import com.tbit.uqbike.client.dao.log.ReturnPosLogDao;
import com.tbit.uqbike.client.dao.ter.TerInfoDao;
import com.tbit.uqbike.client.exception.BaseException;
import com.tbit.uqbike.client.pojo.*;
import com.tbit.uqbike.client.pojo.aliyun.LicensePhotoData;
import com.tbit.uqbike.client.pojo.newEntity.*;
import com.tbit.uqbike.client.pojo.tlv.ReturnBikeData;
import com.tbit.uqbike.client.service.*;
import com.tbit.uqbike.client.service.prohibitarea.CheckProhibitAreaResult;
import com.tbit.uqbike.client.util.*;
import com.tbit.uqbike.client.util.aliyun.OssFileUtils;
import com.tbit.uqbike.client.util.tbit.HuaHuiHttpUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import javax.json.Json;
import java.io.*;
import java.text.DecimalFormat;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.*;

@Service("dispatchConfigService")
@Slf4j
public class DispatchConfigServiceImpl implements DispatchConfigService {
    private static DecimalFormat df = new DecimalFormat(".#########");
    @Autowired
    private DispatchConfigDao dispatchConfigDao;
    @Autowired
    private MachineService machineService;
    @Autowired
    private ParkPointService parkPointService;
    @Autowired
    private GeoService geoService;
    @Autowired
    private MachineStatusService machineStatusService;
    @Autowired
    private TerControlService terControlService;
    @Autowired
    private AdAccountFeeService adAccountFeeService;
    @Autowired
    private AccountConfigService accountConfigService;
    @Autowired
    private ProhibitAreaService prohibitAreaService;
    @Autowired
    private BleBeaconDao bleBeaconDao;
    @Autowired
    private ReturnPosLogDao returnPosLogDao;
    @Autowired
    private MachineBorrowService machineBorrowService;
    @Autowired
    private RedisService redisService;
    @Autowired
    private ParkFeatureService parkFeatureService;
    @Autowired
    private ParkMachineService parkMachineService;
    @Autowired
    private MachineFunctionDao machineFunctionDao;
    @Autowired
    private AccountConfigExtDao accountConfigExtDao;
    @Autowired
    private MachineCheckService machineCheckService;
    @Autowired
    private CacheService cacheService;
    @Autowired
    private OrderReturnDao orderReturnDao;
    @Autowired
    private UserDiscountStatService userDiscountStatService;
    @Autowired
    private TerInfoDao terInfoDao;
    @Autowired
    private LeaderAccountService leaderAccountService;
    @Autowired
    private TerAttrExService terAttrExService;
    @Autowired
    private ReturnBikeMsgLogService returnBikeMsgLogService;
    @Autowired
    private PhotoUploadLogDao photoUploadLogDao;
    @Autowired
    private ColorCodeDao colorCodeDao;
    @Autowired
    private TbitProperties tbitProperties;
    @Autowired
    private RideLogService rideLogService;
    @Autowired
    private AccountDao accountDao;

    @Value("${dispatch.qr.maxDistanceBetweenScanQRBike:100}")
    private double DefaultMaxDistanceBetweenScanQRBike = 100;

    public static void main(String[] args) {
        List<String> list = Arrays.asList("".split(","));
        System.out.println(list.stream().noneMatch(l -> "A1234".endsWith(l)));
    }

    @Override
    public DispatchConfig getByAccountId(Integer accountId) {
        return dispatchConfigDao.getByAccountId(accountId);
    }

    /**
     * 还车调度检查 返回给调度类型 详情见DisPatchConstant常量类
     */
    @Override
    public Dispatch dispatchCheck(Integer machineId, Point point, Map<String, Object> rideMap, Integer force, Integer combineDispatch
            , Integer isLast, Long phoneTimeStamp, Double accuracy, Integer isFrame) {
        //不支持调度，不在还车点内
        Dispatch dispatch = null;

        Machine machine = machineCheck(machineId, point, rideMap);
        MachineBorrow machineBorrow = machineBorrowService.getByMachineId(machine.getMachineId());
        if (machineBorrow == null) {
            throw new BaseException("-3053", "不存在订单数据");
        }

        Integer secondType = null;
        Set<SubDispatch> extraTypes = new HashSet<>();
        Integer helmetMoney = null;
//        log.info("dispatchCheck-----------------------------force:{}",force);
        if (force == 0) {
            Dispatch dispatch1 = helmetCheck(machine, rideMap, machineBorrow, isLast);
//            log.info("头盔锁监测类型------------------------------>dispatch1:{}",JSON.toJSONString(dispatch1));
            if (dispatch1 != null) {
                secondType = dispatch1.getSecondType();
                helmetMoney = dispatch1.getHelmetMoney();
                extraTypes.addAll(dispatch1.getTypes());
                if (DisPatchConstant.DISPATCH_HELMET_EX.equals(dispatch1.getType()) || combineDispatch == 0) {
                    log.info("[{}]下发关闭头盔指令后返回头盔上锁中", machine.getMachineNO());
                    rideMap.put("helmet", dispatch1.getType());
                }
            }
        }
        /**是否启用道钉，如果启用了道钉，需要蓝牙检测*/
        AccountConfig accountConfig = accountConfigService.getByAccountId(machine.getAccountId());
        boolean isBle = false;
        if (accountConfig != null && checkBle(machineId) && (
                accountConfig.getBigParkPoint()
                        .equals(ConfigTypeConstant.ACCOUNT_CONFIG_BLE))) {
            isBle = true;
        }
        /**先判断终端位置，再判断手机位置*/
        Point mnoPoint = null;
        Point checkPoint = null;
        MachineStatus machineStatus = machineStatusService
                .getByMachineNO(machine.getMachineNO(), MapConstant.MAP_GOOGLE);

        /**蓝牙道钉判断*/
        Boolean isBleNode = false;
        if (isBle) {
            /**下发指令控制上传道钉信息*/
            /**检查是否有道钉信息*/
            String tmp = redisService.get(RedisConstant.MACHINE_NO_BLEINFO + machine.getMachineNO());
            if (tmp != null) {
                List<BleInfo> list = JSON.parseArray(tmp, BleInfo.class);
                if (list != null && list.size() > 0) {
                    for (BleInfo bleInfo : list) {
                        if (bleInfo != null && ((System.currentTimeMillis() - bleInfo.getUpdateTime())
                                <= 30000)) {
                            Integer level = getLevel(machine.getAccountId());
                            List<BleNode> ddnodes = bleInfo.getNodes();
                            if (ddnodes.isEmpty()) {
                                continue;
                            }
                            StringBuffer mac = new StringBuffer();
                            for (BleNode b : ddnodes) {
                                /**预置默认值*/
                                redisService
                                        .add(RedisConstant.MACHINE_BLE + machine.getMachineNO(), "mac=" + b.getBleMAC()
                                                + ";rssi=" + b.getBleLevel(), 7 * 24 * 3600);
                                /**缓存还车道钉信息*/
                                SystemData.userBle.put(machineBorrow.getOrderNO(), JSON.toJSONString(bleInfo));
                                if (level == null || level == 0 || level >= Math
                                        .abs(Integer.valueOf(b.getBleLevel()))) {
                                    isBleNode = true;
                                    String start = SystemData.orderTime.get(machineBorrow.getOrderNO());
                                    if (start != null) {
                                        long i = Long.valueOf(start) - bleInfo.getUpdateTime();
                                        SystemData.bleMap.put(machineBorrow.getOrderNO(), i);
                                        /**还车方式 0 终端道钉 1手机道钉*/
                                        SystemData.returnTypeMap.put(machineBorrow.getOrderNO(), 0);
                                    }
                                    mac.append("mac=")
                                            .append(b.getBleMAC())
                                            .append(";rssi=")
                                            .append(b.getBleLevel())
                                            .append(",");
                                }
                            }
                            //兼容金通还车需要推送两个符合条件道钉信号、传递格式mac=xxx;rssi=xxx,mac=xxx;rssi=xxx
                            if (mac.length() > 0) {
                                mac = mac.delete(mac.length() - 1, mac.length());
                                if (!mac.toString()
                                        .contains(",")) {
                                    mac.append(",")
                                            .append(mac);
                                    mac = mac.delete(mac.length() - 1, mac.length());
                                }
                                /**还车道钉信息缓存*/
                                redisService.add(RedisConstant.MACHINE_BLE + machine.getMachineNO(), mac.toString(), 7 * 24 * 3600);
                            }
                        }
                    }
                }
            }

            /**手机道钉检测*/
            Map<String, BleNode> map = new HashMap<>();
            List<String> no = phoneBleCheck(map, machine.getMachineNO(), machine.getAccountId());
            if (no != null && no.size() > 0) {
                /**还车方式 0 终端道钉 1手机道钉*/
                SystemData.returnTypeMap.put(machineBorrow.getOrderNO(), 1);
                isBleNode = true;
            }
            /**生成流水号,预先下发上传道钉信号指令*/
            /**道钉模式*/
            if (accountConfig.getBigParkPoint()
                    .equals(ConfigTypeConstant.ACCOUNT_CONFIG_BLE)) {
                if (isBleNode) {
                    log.info("还车判断存在道钉" + machineBorrow.getOrderNO());
                    if (machineStatus != null && machineStatus.getLon() != null
                            && machineStatus.getLat() != null) {
                        checkPoint = new Point(machineStatus.getLon(), machineStatus.getLat());
                    }
                    Integer parkPointId = parkPointService

                            .checkInParkPoint(machine.getAccountId(), checkPoint, machineId, null);
                    /**缓存还车站点*/
                    if (parkPointId != null) {
                        redisService.add(RedisConstant.USER_PARKPOINTID + machineBorrow.getOrderNO(),
                                String.valueOf(parkPointId), 60);
                    }
                    /**缓存还车位置*/
                    redisService.add(RedisConstant.MACHINE_RETURN_LOCAL + machine.getMachineNO(),
                            JSON.toJSONString(checkPoint), 60);

                    dispatch = new Dispatch(DisPatchConstant.DISPATCH_PARK, null, helmetMoney, secondType);
                    dispatch.getTypes().addAll(extraTypes);
                    return dispatch;
                } else {

                    AdAccountFee adAccountFee = adAccountFeeService
                            .getByAccountId(machine.getAccountId(), machine.getMachineId());
                    if (adAccountFee == null) {

                        dispatch = new Dispatch(DisPatchConstant.DISPATCH_PARK, null, helmetMoney, secondType);
                        dispatch.getTypes().addAll(extraTypes);
                        return dispatch;
                    } else {
                        Integer parkPointId = null;
                        /**兼容未收到道钉信号，判断站点位置*/
                        if (machineStatus != null && machineStatus.getLon() != null && machineStatus.getLat() != null) {
                            checkPoint = new Point(machineStatus.getLon(), machineStatus.getLat());
                            parkPointId = parkCheck(machine, checkPoint, ReturnPosLogConstant.TYPE_MACHINE, machineBorrow);
                        }
                        if (parkPointId == null && point != null && point.getX() != null && point.getY() != null) {
                            parkPointId = parkCheck(machine, point, ReturnPosLogConstant.TYPE_PGONE, machineBorrow);
                        }
                        //log.info("站点id===="+parkPointId);
                        if (parkPointId != null) {
                            redisService.add(RedisConstant.USER_PARKPOINTID + machineBorrow.getOrderNO(),
                                    String.valueOf(parkPointId), 60);
                            redisService
                                    .del(RedisConstant.MACHINE_BLE + machine.getMachineNO());
                            return new Dispatch(DisPatchConstant.DISPATCH_PARK, null);
                        }
                        List<Geo> geos = geoService.getByAccountId(machine.getAccountId());
                        if (geos.size() > 0) {
                            if (machineStatus != null && machineStatus.getLon() != null
                                    && machineStatus.getLat() != null) {
                                mnoPoint = new Point(machineStatus.getLon(), machineStatus.getLat());
                            }
                            checkPoint = point;

                            boolean inArea = isInArea(geos, machine.getAccountId(), checkPoint,
                                    machine.getMachineId());
                            if (inArea) {
                                if (adAccountFee.getParkPointMoney() > 0) {
                                    //支持调度，不在还车点
                                    dispatch = new Dispatch(DisPatchConstant.DISPATCH_NO_IN_PARK,
                                            (double) adAccountFee.getParkPointMoney());
                                } else {
                                    dispatch = new Dispatch(DisPatchConstant.DISPATCH_NOT_RUTURN, null);
                                }
                                dispatch.setSecondType(secondType);
                                dispatch.setHelmetMoney(helmetMoney);
                                return dispatch;
                            } else {
                                if (adAccountFee.getAreaMoney() > 0) {
                                    dispatch = new Dispatch(DisPatchConstant.DISPATCH_NOT_IN_AREA,
                                            (double) adAccountFee.getAreaMoney());
                                } else {
                                    dispatch = new Dispatch(DisPatchConstant.DISPATCH_NOT_RUTURN, null);
                                }
                            }

                        } else {
                            //不支持调度，不在还车点内
                            dispatch = new Dispatch(DisPatchConstant.DISPATCH_PARK, null, helmetMoney, secondType);
                            dispatch.getTypes().addAll(extraTypes);
                            return dispatch;
                        }
                    }
                }
            }
        }

        // 获取区域计费规则
        AdAccountFee adAccountFee = adAccountFeeService
                .getByAccountId(machine.getAccountId(), machine.getMachineId());
        dispatch = parkPointCheck(accountConfig, machineStatus, machine, machineBorrow, rideMap, point,

                isBleNode, adAccountFee, force, isLast, phoneTimeStamp, accuracy, isFrame);

        checkPoint = new Point(machineStatus.getLon(), machineStatus.getLat());
        /**缓存还车位置*/
        redisService.add(RedisConstant.MACHINE_RETURN_LOCAL + machine.getMachineNO(),
                JSON.toJSONString(checkPoint), 60);
        if (dispatch != null) {
            dispatch.setSecondType(secondType);
            dispatch.setHelmetMoney(helmetMoney);
            dispatch.getTypes().addAll(extraTypes);
        }
        return dispatch;
    }

    public Integer parkCheck(Machine machine, Point point, Integer type, MachineBorrow machineBorrow) {
        addReturnLog(point, machine, machineBorrow, type);
        Integer parkPointId = parkPointService
                .checkInParkPoint(machine.getAccountId(), point, machine.getMachineId(), null);
        if (parkPointId != null) {
            ParkFeature parkFeature = parkFeatureService.getByParkPointId(parkPointId, ParkFeatureConstant.PARKFEATURE_TYPE_BLE);
            if (parkFeature == null || parkFeature.getEnable() == 0) {
                return parkPointId;
            }
        }
        return null;
    }

    @Override

    public Dispatch dispatchCheckNew(Integer machineId, Point point, Map<String, Object> rideMap, Integer isLast) {
        Dispatch dispatch = new Dispatch();
        Machine machine = machineCheck(machineId, point, rideMap);
        MachineBorrow machineBorrow = machineBorrowService.getByMachineId(machine.getMachineId());
        if (machineBorrow == null) {
            throw new BaseException("-3053", "不存在订单数据");
        }
        // 获取区域计费规则
        AdAccountFee adAccountFee = adAccountFeeService
                .getByAccountId(machine.getAccountId(), machine.getMachineId());
        /**兼容头盔锁调度费用*/

        Dispatch dispatch1 = helmetCheck(machine, rideMap, machineBorrow, 0);
        if (dispatch1 != null) {
            rideMap.put("helmet", dispatch1.getType());
            dispatch = dispatch1;
            dispatch.setHelmetMoney(adAccountFee.getHelmetMoney());
        }
        AccountConfig accountConfig = accountConfigService.getByAccountId(machine.getAccountId());
        MachineStatus machineStatus = machineStatusService
                .getByMachineNO(machine.getMachineNO(), MapConstant.MAP_GOOGLE);
        Dispatch dispatchDB = parkPointCheck(accountConfig, machineStatus, machine, machineBorrow,

                rideMap, point, false, adAccountFee, 0, isLast, System.currentTimeMillis(), 1.0D, 1);
        dispatch.setMoney(dispatchDB.getMoney());
        return dispatch;
    }

    @Override
    public void discountCheck(Dispatch dispatch, Integer adAccountId, User user,

                              String orderNO, Object helmetSwitch) {
        /**清除调度豁免标识*/
        redisService.del(RedisConstant.RIDELOG_REDUCE + orderNO);
        if (DisPatchConstant.DISPATCH_NO_IN_PARK.equals(dispatch.getType())
                || DisPatchConstant.DISPATCH_NOT_IN_AREA.equals(dispatch.getType())
                || DisPatchConstant.DISPATCH_NO_PARK_AREA.equals(dispatch.getType())
        ) {
            String type = AccountConfigExtConstant.ACCOUNTCONFIG_OUT_OF_PARK;
            Integer discountType = UserDiscountStatConstant.USERDISCOUNTSTAT_OUT_OF_PARK;
            if (DisPatchConstant.DISPATCH_NOT_IN_AREA.equals(dispatch.getType())) {
                type = AccountConfigExtConstant.ACCOUNTCONFIG_OUT_OF_AREA;
                discountType = UserDiscountStatConstant.USERDISCOUNTSTAT_OUT_OF_AREA;
            } else if (DisPatchConstant.DISPATCH_NO_PARK_AREA.equals(dispatch.getType())) {
                type = AccountConfigExtConstant.ACCOUNTCONFIG_OUT_OF_NOPARKAREA;
                discountType = UserDiscountStatConstant.USERDISCOUNTSTAT_OUT_OF_NOPARKAREA;
            }
            /**获取对应区域配置信息*/
            AccountConfigExt accountConfigExt = cacheService
                    .getAccountConfigExtByKey(adAccountId, type);
            //没配置
            if (Objects.isNull(accountConfigExt) || Integer.parseInt(accountConfigExt.getParamValue()) == 0) {
                return;
            }
            // 比较用户豁免次数和配置的豁免次数
            Integer total = Integer.valueOf(accountConfigExt.getParamValue());
            UserDiscountStat userDiscountStat = userDiscountStatService
                    .getByUserId(user.getUserId(), adAccountId, discountType);
            /**站外、超区调度费豁免次数，配置后用户可以在相应的违规次数内减免费用。默认我0，表示犯规即扣款，配置为大于10000时，表示一直豁免*/
            if (userDiscountStat == null || userDiscountStat.getNum() < total || total >= 10000) {
                //学习规范豁免开关
                AccountConfigExt configExt = cacheService.getAccountConfigExtByKey(adAccountId, AccountConfigExtConstant.NEW_USER_FREE_DISPATCH_SWITCH);
                //判断开关是否打开了
                if (Objects.nonNull(configExt) && Integer.parseInt(configExt.getParamValue()) != 0) {
                    //开启了学习规范豁免开关，必须要学习了才能使用豁免
                    dispatch.setStudyToReduceDispatch(true);
                }

                boolean isLeader = false;
                LeaderAccount leaderAccount = leaderAccountService.getByPhone(user.getPhone(), user.getAccountId());
                if (leaderAccount != null && System.currentTimeMillis() < leaderAccount.getExpireTime()
                        .getTime()) {
                    isLeader = true;
                    //如果是领导也不能学习规范
                    dispatch.setStudyToReduceDispatch(false);
                }
                //没开启学习规范豁免开关
                if (!isLeader && !dispatch.isStudyToReduceDispatch()) {
                    dispatch.setReduceMoneyNew(dispatch.getMoney()
                            .intValue());
                    /**添加豁免标识*/
                    redisService
                            .add(RedisConstant.RIDELOG_REDUCE + orderNO, dispatch.getReduceMoney()
                                            .toString(),
                                    60);
                }
            }


        }
    }

    /**
     * 设置缓存有效值
     *
     * @param accountId         区域id
     * @param key               缓存键
     * @param accountConfigType 区域配置类型
     * @param defaultValue      默认值
     */
    private void setRedisKV(Integer accountId, String key, String accountConfigType, String defaultValue) {
        int second = Integer.parseInt(Optional.ofNullable(cacheService.getAccountConfigExtByKey(accountId, accountConfigType))
                .map(AccountConfigExt::getParamValue).orElse(defaultValue));
        redisService.add(key, DateUtil.now(), second * 60);
    }

    /**
     * 站点校验
     */
    public Dispatch parkPointCheck(AccountConfig accountConfig, MachineStatus machineStatus,
                                   Machine machine
            , MachineBorrow machineBorrow, Map<String, Object> rideMap, Point point, boolean isBleNode,

                                   AdAccountFee adAccountFee, Integer force, Integer isLast, Long phoneTimeStamp, Double accuracy, Integer isFrame) {
//        log.info("车辆状态信息{}",JSON.toJSONString(machineStatus));
        /**根据定位有效时间判断使用车辆位置还是手机位置，两个位置都有效时，优先使用车辆位置*/
        // 获取区域配置 - 车辆/手机定位的有效时限，单位是秒，车辆默认 60S ，手机默认10S , 值为0代表关闭
        // 车辆定位有效时间 默认
        int terLocateValidTime = Integer.parseInt(Optional.ofNullable(cacheService.getAccountConfigExtByKey(machine.getAccountId(), AccountConfigExtConstant.TER_LOCATE_VALID_TIME))
                .orElse(new AccountConfigExt(machine.getAccountId(), AccountConfigExtConstant.TER_LOCATE_VALID_TIME, "0", new Date(), "默认配置关闭")).getParamValue());
        // 手机定位有效时间 默认
        int phoneLocateValidTime = Integer.parseInt(Optional.ofNullable(cacheService.getAccountConfigExtByKey(machine.getAccountId(), AccountConfigExtConstant.PHONE_LOCATE_VALID_TIME))
                .orElse(new AccountConfigExt(machine.getAccountId(), AccountConfigExtConstant.PHONE_LOCATE_VALID_TIME, "0", new Date(), "默认配置关闭")).getParamValue());
        // 手机定位gps信号强度 默认
        int phoneGPS = Integer.parseInt(Optional.ofNullable(cacheService.getAccountConfigExtByKey(machine.getAccountId(), AccountConfigExtConstant.PHONE_GPS_VALID_SIGNAL))
                .orElse(new AccountConfigExt(machine.getAccountId(), AccountConfigExtConstant.PHONE_GPS_VALID_SIGNAL, "0", new Date(), "默认配置关闭")).getParamValue());
        // 手机-车辆有效距离
        int validDistance = Integer.parseInt(Optional.ofNullable(cacheService.getAccountConfigExtByKey(machine.getAccountId(), AccountConfigExtConstant.PHONE_MACHINE_VALID_DISTANCE))
                .orElse(new AccountConfigExt(machine.getAccountId(), AccountConfigExtConstant.PHONE_MACHINE_VALID_DISTANCE, "100", new Date(), "默认配置100米")).getParamValue());
        // 手机-车辆有效距离
        String positionSwitch = Optional.ofNullable(cacheService.getAccountConfigExtByKey(machine.getAccountId(), AccountConfigExtConstant.POSITION_SWITCH))
                .orElse(new AccountConfigExt(machine.getAccountId(), AccountConfigExtConstant.POSITION_SWITCH, "0", new Date(), "默认判定车和人的位置")).getParamValue();
        // 获取当前时间戳
        long currentTimeMillis = System.currentTimeMillis();
        // 车辆定位时间戳
        String terPosDt = Objects.isNull(machineStatus.getPosDt()) ? "2000-01-01 00:00:00" : machineStatus.getPosDt();
        long terPosDtMillis = LocalDateTime.parse(terPosDt, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")).atZone(ZoneId.of("Asia/Shanghai")).toInstant().toEpochMilli();
        //无定位自行车设备还车不用终端位置
        Map<String, Object> map = new HashMap<>();
        map.put("machineId", machine.getMachineId());
        map.put("functionType", MachineStateConstant.MACHINE_TYPE_NOPOINT);
        MachineFunction machineFunctionDB = machineFunctionDao.getByMachineId(map);
        boolean isNoLocationMachine = (machineFunctionDB != null && machineFunctionDB.getFunctionValue().equals(1));

        // 手机/车辆定位是否有效
        boolean validPhone = true;
        boolean validMachine = true;
        // 人车位置开关
        boolean onlyPersonLocation = Objects.equals(positionSwitch, "2");
        boolean onlyMachineLocation = Objects.equals(positionSwitch, "1");
        if ( (terLocateValidTime != 0 && currentTimeMillis - terPosDtMillis > terLocateValidTime * 1000L) || onlyPersonLocation) {
            validMachine = false;
        }
        if ((phoneLocateValidTime != 0 && currentTimeMillis - phoneTimeStamp > phoneLocateValidTime * 1000L) || (phoneGPS != 0 && accuracy >= phoneGPS) || onlyMachineLocation) {
            validPhone = false;
        }
        // 如果车辆功能配置了无定位车辆那么则允许使用手机位置
        if (isNoLocationMachine){
            validPhone = true;
        }
        log.info("当前还车使用的定位模式{}", onlyPersonLocation ? "仅使用人的位置" : onlyMachineLocation ? "仅使用车辆的位置" : "同时使用人和车辆位置");
        Account account = accountDao.getByAccountId(machine.getAccountId());
        log.info("区域={}，isLast={},订单{}，还车站点判断，还车时间{}, [ 用户手机定位{}，手机定位时间{},手机有效时间差{}S，手机定位精度{}米，配置的有效精度{}米，手机定位是否有效{} ]，" +
                        " [ 车辆定位{}，车辆定位时间{},车辆有效时间差{}S,车辆定位是否有效{} ] ",
                account.getName(), isLast, machineBorrow.getOrderNO(), currentTimeMillis, JSON.toJSONString(point), phoneTimeStamp
                , phoneLocateValidTime, accuracy, phoneGPS, validPhone, JSON.toJSONString(machineStatus), terPosDt, terLocateValidTime, validMachine);

        if (!validPhone && !validMachine) {
            log.warn("区域={}，订单{}，手机和车辆定位最终有效性校验均未通过，isLast={}", account.getName(), machineBorrow.getOrderNO(), isLast);
            // 不让还车
            throw new BaseException("手机和车辆定位已过期，请开启手机定位并寻找信号良好的地方进行重试");
        }

        Dispatch dispatch;
        Point mnoPoint = null;
        /**定位站点ID**/
        Integer parkPointId = null;
        /**定位站点ID-中控**/
        Integer parkPointIdByMachine = null;
        /**定位站点ID-手机**/
        Integer parkPointIdByPhone = null;
        /**检查点**/
        Point checkPoint = null;
        /**检查点-中控**/
        Point checkPointByMachine = null;
        /**检查点-手机**/
        Point checkPointByPhone = null;
        /**禁停区标志,默认非禁停区*/
        Boolean isBan = false;
        /**兼容禁停区模式,1为禁停区*/
        if (accountConfig != null && accountConfig.getReturnModel() == 1) {
            isBan = true;
        }

        boolean enableNoParkArea = Optional.ofNullable(adAccountFee)
                .map(AdAccountFee::getNoParkAreaSwitch)
                .map(s -> s == 1)
                .orElse(false);

        //log.info("adAccountFee:{}", adAccountFee);
        Integer noParkAreaMoney = Optional.ofNullable(adAccountFee)
                .map(AdAccountFee::getNoParkAreaMoney)
                .orElse(0);
        Integer prohibitAreaId = null;
        CheckProhibitAreaResult checkInProhibitArea = null;

        /**订单时间**/
        Date returnTime = new Date();
        //还车距离
        Map<String, Double> distanceMap = new HashMap<>();

        //RGPS定位是否有效
        boolean validRGPS = false;
        String gprsTimes = redisService.get(RedisConstant.RETURN_RGPS_TIMES + machineBorrow.getOrderNO());
        //是否开启rgps功能
        Map<String, Object> gpsMap = new HashMap<>();
        gpsMap.put("machineId", machine.getMachineId());
        gpsMap.put("functionType", MachineStateConstant.MACHINE_TYPE_RGPS);
        Boolean rgps_flag = Optional.ofNullable(machineFunctionDao.getByMachineId(gpsMap))
                .map(x -> x.getFunctionValue() == 1).orElse(false);
        RideLog rideLog = cacheService.getBorrowInfo(null, machine.getMachineId());
        /** 如果得到的终端最后状态不为空并且经纬度不为空，判断是在站点还是禁停区，获取parkPointId */
        if (machineStatus != null && machineStatus.getLon() != null && machineStatus.getLat() != null && (machineFunctionDB == null
                || machineFunctionDB.getFunctionValue()
                .equals(0)) && validMachine) {
            if (machineStatus.getPosType() == null) {
                machineStatus.setPosType(0);
            }
            // 车辆位置
            checkPoint = new Point(machineStatus.getLon(), machineStatus.getLat());
            mnoPoint = new Point(machineStatus.getLon(), machineStatus.getLat());
            //两分钟内还车不启用RGPS
            boolean isFreeRide = Optional.ofNullable(rideLog).map(
                    x -> DateUtil.between(DateUtil.parse(x.getStartTime()), new Date(), DateUnit.MINUTE) <= 2).orElse(false);
            //RGPS逻辑，开启rgps功能
            if (rgps_flag && !isFreeRide) {
                String rgps_value = redisService.get(RedisConstant.RETURN_RGPS + machine.getMachineNO());
                if (Objects.isNull(gprsTimes) && isFrame > 0) {
                    setRedisKV(machine.getAccountId(), RedisConstant.RETURN_RGPS_TIMES + machineBorrow.getOrderNO(),
                            AccountConfigExtConstant.ACCOUNTCONFIG_RGPS_TIMES, "5");
                }
                if (isFrame == 0 && StringUtils.isBlank(rgps_value)) {
                    //一直未返回值，且为最后一次轮询，则返回RGPS校验未通过
                    if (isLast == 1) {
                        throw new BaseException("-40006", "RGPS校验未通过");
                    }
                    //为空首次默认返回站外调度
                    return new Dispatch(DisPatchConstant.DISPATCH_NO_IN_PARK, Optional.ofNullable(adAccountFee)
                            .map(AdAccountFee::getParkPointMoney)
                            .orElse(0).doubleValue());
                }
                log.info("订单{},RGPS结果{}", machineBorrow.getOrderNO(), rgps_value);
                if (StringUtils.isNotBlank(rgps_value)) {
                    if ("V".equals(rgps_value)) {
                        if (isFrame == 0 && StringUtils.isBlank(gprsTimes)) {
                            throw new BaseException("-40006", "RGPS校验未通过");
                        }
                    } else {
                        //075227.000 E106.478183 N26.578711
                        String[] rgps_arr = rgps_value.split(" ");
                        if (rgps_arr.length == 3) {
                            double lon = Double.parseDouble(rgps_arr[1].replace("E", ""));
                            double lat = Double.parseDouble(rgps_arr[2].replace("N", ""));
                            checkPoint = new Point(lon, lat);
                            mnoPoint = new Point(lon, lat);
                            validRGPS = true;
                        }
                    }
                }
            }


            /**还车记录还车位置*/
            addReturnLog(checkPoint, machine, machineBorrow, ReturnPosLogConstant.TYPE_MACHINE);
            if (machineStatus.getPosType() == null) {
                machineStatus.setPosType(0);
            }
            distanceMap.put("posType", machineStatus.getPosType().doubleValue());
            log.info(
                    "[" + machine.getMachineNO() + "]判断终端位置是否在站点,lo=" + checkPoint.getX() + ",la="
                            + checkPoint.getY());
            //判断是否在禁停区,与 是否是禁停区模式无关
            checkInProhibitArea = prohibitAreaService.checkInProhibitArea(machine.getAccountId(), checkPoint);
            prohibitAreaId = checkInProhibitArea.inProhibitArea() ? checkInProhibitArea.getProhibitArea().getProhibitAreaId() : null;
            //非禁停区模式，查询站点
            if (!isBan) {
                parkPointId = parkPointService.checkInParkPoint(machine.getAccountId(), checkPoint, machine.getMachineId(), distanceMap);
                rideMap.put("machineLon", machineStatus.getLon());
                rideMap.put("machineLat", machineStatus.getLat());
                rideMap.put("posType", machineStatus.getPosType());
                if (distanceMap.get("returnDistance") != null) {
                    rideMap.put("nearParkId", distanceMap.get("parkPointId")
                            .intValue());
                    rideMap.put("machineDistance", distanceMap.get("returnDistance"));
                }
                if (parkPointId != null) {
                    rideMap.put("machinePark", parkPointId);
                }
                /**道钉绑定站点*/
                Integer blePointId = machineService
                        .isInBleBeacon(machine.getMachineNO(), machine.getAccountId(),
                                machineBorrow.getOrderNO());
                if (blePointId != null) {
                    rideMap.put("assistPark", blePointId);
                }
                parkPointId = parkCheck(blePointId, machineStatus, parkPointId, machineBorrow, machine);
            }
            //记录还车手机位置跟车辆位置距离
            if (point != null && point.getX() != null && point.getY() != null) {
                rideMap.put("machinePhoneDistance", GeoUtil.getPointDistance(new Point(machineStatus.getLon(), machineStatus.getLat()), point));
            }
            parkPointIdByMachine = parkPointId;
            if (isLast == 1 || parkPointId != null) {
                //添加终端位置意向还车日志
                returnBikeMsgLogService.addBikeMsgLog(machineBorrow, machine, ReturnBikeMsgLogConstant.POS_FROM_MACHINE, ReturnBikeMsgLogConstant.TYPE_PURPOSE, parkPointId, distanceMap, returnTime, checkPoint);
            }
        }

        /**parkPointId为空，不在站点，判断手机位置*/
        double distance = 0D;
        boolean ishigh = false;
        if (point != null && point.getX() != null && point.getY() != null) {
            rideMap.put("phoneDistance", GeoUtil.getPointDistance(new Point(machineStatus.getLon(), machineStatus.getLat()), point));
        }
        if ((parkPointId == null && prohibitAreaId == null) && validRGPS && !validPhone && isFrame == 0 && StringUtils.isBlank(gprsTimes)) {
            log.info("订单{},validPhone校验未通过", machineBorrow.getOrderNO());
            setRedisKV(machine.getAccountId(), RedisConstant.RETURN_RGPS_TIMES + machineBorrow.getOrderNO(),
                    AccountConfigExtConstant.ACCOUNTCONFIG_RGPS_TIMES, "5");
            throw new BaseException("-40006", "RGPS校验未通过");
        }

        /**parkPointId为空，不在站点，prohibitAreaId，不在禁停区，判断手机位置*/
        if ((parkPointId == null && prohibitAreaId == null) && point != null && point.getX() != null && point.getY() != null && validPhone) {

            /**默认客户手机位置有效*/
            /**如果车辆位置存在，并且车辆电量时间距离当前时间小于5分钟，则需要考虑还车手机位置无效情况,即手机位置和车辆位置距离大于100米，认为无效*/
            if (machineStatus != null && machineStatus.getBatDt() != null
                    && DateTimeUtil.getMinDiffer(machineStatus.getBatDt()) < 5
                    && machineStatus.getLon() != null && machineStatus.getLat() != null) {
                distance = GeoUtil
                        .getDistanceBy(machineStatus.getLon(), machineStatus.getLat(), point.getX(),
                                point.getY());
            }
            /**高精度兼容手机不使用*/
            Map<String, Object> param = new HashMap<>();
            param.put("machineId", machine.getMachineId());
            param.put("functionType", MachineStateConstant.MACHINE_TYPE_HIGH);
            MachineFunction machineFunction = machineFunctionDao.getByMachineId(param);

            if (machineFunction != null && machineFunction.getFunctionValue() == 1) {
                distance = 200;
                if (machineStatus.getPosType() == 2) {
                    ishigh = true;
                }
            }
            // 当仅使用人的位置的时候，不校验人车位置距离合法性
            if (distance < validDistance || machine.getMachineType()
                    .equals(MachineStateConstant.MACHINE_TYPE_BIKE) || onlyPersonLocation ) {
                checkPoint = point;
                log.info(
                        "[" + machine.getMachineNO() + "]判断手机位置是否在站点,lo=" + checkPoint.getX()
                                + ",la=" + checkPoint.getY());
                checkInProhibitArea = prohibitAreaService.checkInProhibitArea(machine.getAccountId(), point);
                prohibitAreaId = checkInProhibitArea.inProhibitArea() ? checkInProhibitArea.getProhibitArea().getProhibitAreaId() : null;
                //非禁停区模式，查询站点
                if (!isBan) {
                    parkPointId = parkPointService.checkInParkPoint(machine.getAccountId(), point, machine.getMachineId(), distanceMap);
                    if (parkPointId != null) {
                        rideMap.put("phonePark", parkPointId);
                    }
                    /**道钉绑定站点*/
                    Integer blePointId = machineService
                            .isInBleBeacon(machine.getMachineNO(), machine.getAccountId(),
                                    machineBorrow.getOrderNO());
                    parkPointId = parkCheck(blePointId, machineStatus, parkPointId, machineBorrow, machine);
                }
                parkPointIdByPhone = parkPointId;
                if (isLast == 1 || parkPointId != null) {
                    //添加手机位置意向还车日志
                    returnBikeMsgLogService.addBikeMsgLog(machineBorrow, machine, ReturnBikeMsgLogConstant.POS_FROM_PHONE, ReturnBikeMsgLogConstant.TYPE_PURPOSE, parkPointId, distanceMap, returnTime, checkPoint);
                }
            } else {

                log.info(machine.getMachineNO() + "手机位置距离车辆过远，距离" + distance);
            }
            /**还车记录还车位置*/
            addReturnLog(point, machine, machineBorrow, ReturnPosLogConstant.TYPE_PGONE);
        }
        log.info("车辆位置{}，checkPoint位置{}", JSON.toJSONString(mnoPoint), JSON.toJSONString(checkPoint));

        /**--------------------------------------------融合定位包逻辑开始--------------------------------------------**/
        /**融合定位包数据**/
        Date date = new Date();
        /**1.手机融合定位包数据**/
        String phoneRedisKey = RedisConstant.PHONE_FUSELOCATION_DATA + machine.getMachineNO();
        List<PhoneFuselocationData> phoneDataList = Optional.ofNullable(redisService.get(phoneRedisKey))
                .map(o -> JSONObject.parseArray(o, PhoneFuselocationData.class))
                .orElseGet(() -> null);
        Integer phonePointId = getMaxSizeParkPointByPhone(phoneDataList, date, 1);
        /**2.中控融合定位包数据**/
        String centralControlRedisKey = RedisConstant.CENTRALCONTROL_FUSELOCATION_DATA + machine.getMachineNO();
        List<BleNode> centralControlDataList = Optional.ofNullable(redisService.get(centralControlRedisKey))
                .map(o -> JSONObject.parseArray(o, BleNode.class))
                .orElseGet(() -> null);
        Integer centralControlPointId = getMaxSizeParkPointByCentralControl(centralControlDataList, date, 1);

        /**仅使用手机位置*/
        Boolean onlyPhone = false;
        /**可以使用手机位置*/
        Boolean canPhone = false;
        /**1、只使用手机位置
         设备离线：最后信号时间在5分钟之外，或者设备最新信号时间早于订单开始时间。
         中控位置无效：最后定位时间在5分钟外且定位时间小于订单开始时间**/
        Boolean batDtInFiveMin = Optional.ofNullable(machineStatus)
                .map(o -> o.getBatDt())
                .filter(o -> DateTimeUtil.getMinDiffer(o) <= 5)
                .map(o -> true)
                .orElseGet(() -> false);

        Boolean posDtInFiveMin = Optional.ofNullable(machineStatus)
                .map(o -> o.getPosDt())
                .filter(o -> DateTimeUtil.getMinDiffer(o) <= 5)
                .map(o -> true)
                .orElseGet(() -> false);


        Boolean beforStartTime = DateTimeUtil.getSecondDiffer(rideLog.getStartTime(), machineStatus.getBatDt()) < 0;

        if ((!batDtInFiveMin) || beforStartTime || ((!posDtInFiveMin) && beforStartTime)) {
            /**只使用手机位置**/
            onlyPhone = true;
        }
        /**2、终端最后定位时间在30秒内，如果手机位置在站点，车辆位置不在站点，则两都不超过N（150）米，则可使用手机位置**/
//    /**手机在车辆附近2:手机融合定位包包含用户借车中控编号，则可使用手机位置**/
        AccountConfigExt fuselocationmileage = cacheService.getAccountConfigExtByKey(machine.getAccountId(),
                AccountConfigExtConstant.FUSE_LOCATION_MILEAGE);
        Integer mileage = Optional.ofNullable(fuselocationmileage)
                .map(o -> Integer.valueOf(o.getParamValue()))
                .orElseGet(() -> 150);

        Boolean postDtInThirtySec = Optional.ofNullable(machineStatus)
                .map(o -> o.getPosDt())
                .filter(o -> DateTimeUtil.getSecondDiffer(o) <= 30)
                .map(o -> true)
                .orElseGet(() -> false);

        if (postDtInThirtySec && null != parkPointIdByPhone && null == parkPointIdByMachine) {
            if (distance <= mileage) {
                /**可使用手机位置**/
                canPhone = true;
            }
        }

        String phonePointName = Optional.ofNullable(phonePointId)
                .map(o -> parkPointService.getByParkPointId(o))
                .map(o -> o.getName())
                .orElseGet(() -> null);
        String centralControlPointName = Optional.ofNullable(centralControlPointId)
                .map(o -> parkPointService.getByParkPointId(o))
                .map(o -> o.getName())
                .orElseGet(() -> null);

        if (null == parkPointId) {
            Map logData = new HashMap();
            logData.put("data_phone", JSON.toJSONString(phoneDataList));
            logData.put("data_centralcontrol", JSON.toJSONString(centralControlDataList));
            logData.put("parkPoint", parkPointId);
            logData.put("parkpointId_phone", phonePointId);
            logData.put("parkpointId_centralcontrol", centralControlPointId);
            logData.put("parkpointName_phone", phonePointName);
            logData.put("parkpointName_centralcontrol", centralControlPointName);
            logData.put("orderNo", machineBorrow.getOrderNO());
            logData.put("time", date);
            logData.put("machineNO", machine.getMachineNO());
            logData.put("accountUserId", machine.getAccountId());
            logData.put("userCode", machine.getUserCode());
            if (null != phonePointId || null != centralControlPointId) {
                logData.put("flag", true);
            } else {
                logData.put("flag", false);
            }
            terControlService.send(
                    RabbitMQData.queueExchange, RabbitMQData.slskey, String.valueOf(GatewayProtocol.MQ_MSG_ID_SLS_LOG_RHDWB_PUSH), "", JSON.toJSONString(logData));

        }
        /**获取融合定位包辅助还车开关*/
        AccountConfigExt ext = cacheService.getAccountConfigExtByKey(machine.getAccountId(),
                AccountConfigExtConstant.FUSE_LOCATION);
        Boolean isFuselocation = null != ext && Objects.equals(ext.getParamValue(), "1") ? true : false;
        if (isFuselocation && null == parkPointId && !ishigh) {
            parkPointId = null != phonePointId ? phonePointId : centralControlPointId;
            if (isLast == 1 || parkPointId != null) {
                //添加融合定位包位置意向还车日志
                if (Objects.nonNull(checkPoint)) {
                    returnBikeMsgLogService.addBikeMsgLog(machineBorrow, machine, ReturnBikeMsgLogConstant.POS_FROM_FUSE, ReturnBikeMsgLogConstant.TYPE_PURPOSE, parkPointId, distanceMap, returnTime, checkPoint);
                }
            }
        }
        log.info("订单号 [" + machineBorrow.getOrderNO() + "]"
                + "手机站点 [" + parkPointIdByPhone + "]"
                + "车辆站点 [" + parkPointIdByMachine + "]"
                + "手机融合包站点 [" + phonePointId + "]"
                + "中控融合包站点 [" + centralControlPointId + "]"
                + "最终站点 [" + parkPointId + "]"
                + "可使用手机[" + onlyPhone + "]"
                + "仅使用手机[" + canPhone + "]");
        /**--------------------------------------------融合定位包逻辑结束--------------------------------------------**/

        /**parkPointId不为空，在站点，则缓存还车站点*/
        if (parkPointId != null) {
            redisService.add(RedisConstant.USER_PARKPOINTID + machineBorrow.getOrderNO(),
                    String.valueOf(parkPointId), 60);
            rideMap.put("parkPointId", parkPointId);
        }
        //prohibitAreaId不为空，在禁停区，则缓存禁停区id
        if (prohibitAreaId != null) {
            redisService.add(RedisConstant.USER_PROHIBITAREAID + machineBorrow.getOrderNO(),
                    String.valueOf(prohibitAreaId), 60);
            rideMap.put("prohibitAreaId", prohibitAreaId);
        }
        /** 蓝牙道钉判断 */
        if (isBleNode) {
            log(machineBorrow.getOrderNO(), machine.getMachineId(), machine.getUserCode(), rideMap, checkInProhibitArea, isBan, enableNoParkArea);
            return new Dispatch(DisPatchConstant.DISPATCH_PARK, null);
        }


        /** 站点id不为空，在站点，判断是否需要调度 */
        if (parkPointId != null && isBan == false) {

            /**获取站点容量*/
            ParkPoint parkPoint = parkPointService.getByParkPointId(parkPointId);
            Integer count = parkMachineService.getParkPointCanBorrowNum(parkPointId);
            log.info("满桩判断,站点id:{},在站点,判断是否需要调度,parkPoint:{},count:{},adAccountFee:{}", parkPointId, JSON.toJSONString(parkPoint), count, JSON.toJSONString(adAccountFee));
            // 区域计费规则不为空
            if (adAccountFee != null && adAccountFee.getDispatchSwitch() == 1
                    && adAccountFee.getParkPointMoney() > 0 && parkPoint != null && parkPoint.getCapacity() > 0
                    && count >= parkPoint.getCapacity()) {
                // 3.1.2新增，判断超出站点最大容量可以强制还车，收取站外还车调度费
                log.info("满桩判断,判断超出站点最大容量可以强制还车,收取站外还车调度费");
                log(machineBorrow.getOrderNO(), machine.getMachineId(), machine.getUserCode(), rideMap, checkInProhibitArea, isBan, enableNoParkArea);
                return new Dispatch(DisPatchConstant.DISPATCH_OVERFLOW_PARK,
                        (double) adAccountFee.getParkPointMoney());

            }

            /**在站点，需要判断是否站点有绑定道钉*/
            List<BleBeacon> bleBeacons = bleBeaconDao.getByParkPointId(parkPointId);
            // 有蓝牙道钉的站点
            if (bleBeacons.size() > 0 && accountConfig != null && accountConfig.getBigParkPoint()
                    .equals(ConfigTypeConstant.ACCOUNT_CONFIG_BLE)) {
                if (adAccountFee == null) {
                    //不支持调度，不在还车点内
                    log(machineBorrow.getOrderNO(), machine.getMachineId(), machine.getUserCode(), rideMap, checkInProhibitArea, isBan, enableNoParkArea);
                    return new Dispatch(DisPatchConstant.DISPATCH_NOT_RUTURN, null);
                } else {
                    /**开关打开，金额大于0*/
                    if (adAccountFee.getDispatchSwitch() == 1) {
                        if (adAccountFee.getParkPointMoney() > 0) {
                            //支持调度，不在还车点
                            log(machineBorrow.getOrderNO(), machine.getMachineId(), machine.getUserCode(), rideMap, checkInProhibitArea, isBan, enableNoParkArea);
                            return new Dispatch(DisPatchConstant.DISPATCH_NO_IN_PARK,
                                    (double) adAccountFee.getParkPointMoney());
                        } else {
                            //不支持调度，不在还车点内
                            log(machineBorrow.getOrderNO(), machine.getMachineId(), machine.getUserCode(), rideMap, checkInProhibitArea, isBan, enableNoParkArea);
                            return new Dispatch(DisPatchConstant.DISPATCH_NOT_RUTURN, null);
                        }

                    }
                }
            }
            // 垂直停车判断
            dispatch = machineService.isVertical(accountConfig, machine, machineBorrow.getUserId(),
                    machineBorrow.getOrderNO(), parkPointId, null, isLast);
            if (dispatch != null && dispatch.getType()
                    .equals(DisPatchConstant.DISPATCH_VERTICAL) && force == 0) {
                log(machineBorrow.getOrderNO(), machine.getMachineId(), machine.getUserCode(), rideMap, checkInProhibitArea, isBan, enableNoParkArea);
                return dispatch;
            }
            /**摄像头条件判断*/
            dispatch = machineCheckService
                    .checkWebcam(machine.getAccountId(), machine.getMachineNO(), parkPointId,
                            null, isLast);
            if (dispatch != null && dispatch.getType()
                    .equals(DisPatchConstant.DISPATCH_SXT) && force == 0) {
                log(machineBorrow.getOrderNO(), machine.getMachineId(), machine.getUserCode(), rideMap, checkInProhibitArea, isBan, enableNoParkArea);
                return dispatch;
            }
            /**rfid条件判断*/
            dispatch = machineCheckService
                    .checkRfid(machine.getAccountId(), machine.getMachineNO(), parkPointId,
                            null, isLast);
            if (dispatch != null && dispatch.getType()
                    .equals(DisPatchConstant.DISPATCH_RFID) && force == 0) {
                log(machineBorrow.getOrderNO(), machine.getMachineId(), machine.getUserCode(), rideMap, checkInProhibitArea, isBan, enableNoParkArea);
                return dispatch;
            }
            /**jc条件判断*/
            dispatch = machineCheckService
                    .checkJc(machine.getAccountId(), machine.getMachineNO(), parkPointId,
                            null, isLast);
            if (dispatch != null && dispatch.getType()
                    .equals(DisPatchConstant.DISPATCH_JC) && force == 0) {
                log(machineBorrow.getOrderNO(), machine.getMachineId(), machine.getUserCode(), rideMap, checkInProhibitArea, isBan, enableNoParkArea);
                return dispatch;
            }
            /**充电桩条件判断*/
            dispatch = machineCheckService
                    .checkCdz(machine.getAccountId(), machine.getMachineNO(), parkPointId,
                            machineBorrow.getOrderNO(), isLast);
            if (dispatch != null && dispatch.getType()
                    .equals(DisPatchConstant.DISPATCH_CDZ) && force == 0) {
                log(machineBorrow.getOrderNO(), machine.getMachineId(), machine.getUserCode(), rideMap, checkInProhibitArea, isBan, enableNoParkArea);
                return dispatch;
            }
            /**拍照还车检验*/
            dispatch = machineCheckService
                    .checkPhonePhoto(machine.getAccountId(), machine, parkPointId,
                            machineBorrow.getOrderNO(), isLast, machineBorrow.getUserId(), rideLog);
            if (dispatch != null && (!dispatch.getType()
                    .equals(DisPatchConstant.DISPATCH_PARK)) && force == 0) {
                log(machineBorrow.getOrderNO(), machine.getMachineId(), machine.getUserCode(), rideMap, checkInProhibitArea, isBan, enableNoParkArea);
                return dispatch;
            }

            //同时满足在站点和在禁停区(开关开启的情况下)，优先在站点
            log(machineBorrow.getOrderNO(), machine.getMachineId(), machine.getUserCode(), rideMap, checkInProhibitArea, isBan, enableNoParkArea);
            return new Dispatch(DisPatchConstant.DISPATCH_PARK, null);
        }
        List<Geo> geos = geoService.getByAccountId(machine.getAccountId());

        if (Objects.isNull(mnoPoint) && Objects.isNull(checkPoint)) {
            log.error("订单 {} machineId {} userCode {} " +
                    "车辆定位和手机定位均无效", machineBorrow.getOrderNO(), machine.getMachineId(), machine.getUserCode());
            throw new BaseException("手机定位信号弱/距离车辆过远");
        }

        //车辆是否在区域内
        boolean isInGeo = isInGeo(geos, mnoPoint, checkPoint);

        if (judgeFreeRide(geoService.getByAccountId(machine.getAccountId()), machine.getAccountId(), machine.getMachineId(), rideLog.getUserId())) {
            log(machineBorrow.getOrderNO(), machine.getMachineId(), machine.getUserCode(), rideMap, checkInProhibitArea, isBan, enableNoParkArea);
            // 免费骑行不收取调度费开关
            return new Dispatch(DisPatchConstant.DISPATCH_PARK, null);
        }

        if (prohibitAreaId != null && enableNoParkArea) {
            log(machineBorrow.getOrderNO(), machine.getMachineId(), machine.getUserCode(), rideMap, checkInProhibitArea, isBan, enableNoParkArea);
            return new Dispatch(DisPatchConstant.DISPATCH_NO_PARK_AREA, noParkAreaMoney.doubleValue());
        }
        //在禁停区但是没有开启禁停区调度费开关，不允许还车
        if (prohibitAreaId != null && !enableNoParkArea) {
            log(machineBorrow.getOrderNO(), machine.getMachineId(), machine.getUserCode(), rideMap, checkInProhibitArea, isBan, enableNoParkArea);
            return new Dispatch(DisPatchConstant.DISPATCH_NOT_RUTURN, null);
        }
        if (isBan && isInGeo) {
            //禁停区模式，不在禁停区，可以还车
            log(machineBorrow.getOrderNO(), machine.getMachineId(), machine.getUserCode(), rideMap, checkInProhibitArea, isBan, enableNoParkArea);
            return new Dispatch(DisPatchConstant.DISPATCH_PARK, null);
        }


        /**判断代理商是否设置运营区域*/

        if (geos.size() > 0) {
            dispatch = isInAreaOld(geos, machine.getAccountId(), mnoPoint, checkPoint,
                    machine.getMachineId());
        } else {
            //不支持调度，不在还车点内
            dispatch = new Dispatch(DisPatchConstant.DISPATCH_NOT_RUTURN, null);
        }
        /*if (geos.size() > 0 && isInArea(geos, machine.getAccountId(), checkPoint,
                machine.getMachineId())) {
            if (adAccountFee.getParkPointMoney() > 0) {
                //支持调度，不在还车点
                log(machineBorrow.getOrderNO(), machine.getMachineId(), machine.getUserCode(), rideMap, checkInProhibitArea,isBan,enableNoParkArea);
                dispatch = new Dispatch(DisPatchConstant.DISPATCH_NO_IN_PARK,
                        (double) adAccountFee.getParkPointMoney());
            } else {
                log(machineBorrow.getOrderNO(), machine.getMachineId(), machine.getUserCode(), rideMap, checkInProhibitArea,isBan,enableNoParkArea);
                dispatch = new Dispatch(DisPatchConstant.DISPATCH_NOT_RUTURN, null);
            }
        } else {
            //不支持调度，不在还车点内
            log(machineBorrow.getOrderNO(), machine.getMachineId(), machine.getUserCode(), rideMap, checkInProhibitArea,isBan,enableNoParkArea);
            dispatch = new Dispatch(DisPatchConstant.DISPATCH_NOT_RUTURN, null);
        }*/
        log(machineBorrow.getOrderNO(), machine.getMachineId(), machine.getUserCode(), rideMap, checkInProhibitArea, isBan, enableNoParkArea);
        return dispatch;
    }

    private void log(String orderNo, Integer machineId, String userCode, Map rideMap, CheckProhibitAreaResult checkInProhibitArea, boolean isBan, boolean enableNoParkArea) {
        log.info("订单 {} machineId {} userCode {} " +
                "是否是禁停区模式:{}" +
                "是否开启禁停区调度费:{}" +
                "骑行信息:{},禁停区信息:{}", orderNo, machineId, userCode, isBan, enableNoParkArea, JSON.toJSONString(rideMap), JSON.toJSON(checkInProhibitArea));
    }

    private boolean judgeFreeRide(List<Geo> geos, Integer accountId, Integer machineId, Integer userId) {
        Geo geo = geos.get(0);
        if (geo == null || geo.getGeoSwitch() != 1) {
            return false;
        }

        AdAccountFee adAccountFee = adAccountFeeService.getByAccountId(accountId, machineId);
        if (adAccountFee == null ||
                (!Objects.equals(adAccountFee.getDispatchSwitch(), 1) && !Objects.equals(adAccountFee.getNoParkAreaSwitch(), 1))) {
            return false;
        }
        AccountConfigExt accountConfigExt = cacheService.getAccountConfigExtByKey(accountId, AccountConfigExtConstant.ACCOUNTCONFIG_FREE_RIDE);
        if (accountConfigExt == null || !"1".equals(accountConfigExt.getParamValue())) {
            return false;
        }

        RideLog rideLog = cacheService.getBorrowInfo(null, machineId);
        if (rideLog == null) {
            throw new BaseException("-3006", "骑行订单已经结束");
        }
        Integer toDayFreeRideCount = rideLogService.getTodayNo(userId, DateTimeUtil.getNowTimeYMD());
        if (toDayFreeRideCount < Optional.ofNullable(adAccountFee.getFreeRideTime()).orElse(0)) {
            Integer rideTime = DateTimeUtil.getMinDiffer(rideLog.getStartTime());
            return rideTime <= adAccountFee.getFreeTime();
        }

        return false;
    }

    /**
     * 根据融合包数据返回出现次数最多的站点信息
     **/
    private Integer getMaxSizeParkPointByPhone(List<PhoneFuselocationData> oldList, Date date, Integer minMachineCount) {
        if (CollectionUtil.isEmpty(oldList)) {
            return null;
        }
        /**机器编码，站点ID**/
        Map<String, Integer> map = getParkPointMapByPhone(oldList, date);
        /**站点ID，融合定位包数据**/
        Map<Integer, ArrayList<Integer>> dataMap = new HashMap<>();//站点及rssi集合
        for (PhoneFuselocationData o : oldList) {
            Integer parkPointId = map.get(o.getMachineNO());
            if (null != parkPointId) {
                //统计
                ArrayList<Integer> list = dataMap.get(parkPointId) == null ? new ArrayList<Integer>() : dataMap.get(parkPointId);
                /**rssi取绝对值**/
                list.add(Math.abs(o.getRssi()));
                dataMap.put(parkPointId, list);
            }
        }
        /**平均rssi**/
        double max = 0d;
        /**站点id**/
        Integer max_s = null;
        Iterator<Map.Entry<Integer, ArrayList<Integer>>> iterator = dataMap.entrySet()
                .iterator();
        while (iterator.hasNext()) {
            Map.Entry<Integer, ArrayList<Integer>> next = iterator.next();
            Integer key = next.getKey();
            ArrayList<Integer> value = next.getValue();
            if (value.size() >= minMachineCount) {
                /**计算平均rssi**/
                //Integer类型求平均值
                double average = value.stream()
                        .mapToDouble(Integer::intValue)
                        .average()
                        .getAsDouble();
                if (average > max) {
                    max = average;
                    max_s = key;
                }
            }
        }
        return max_s;
    }

    /**
     * 根据融合包数据返回出现次数最多的站点信息
     **/
    private Integer getMaxSizeParkPointByCentralControl(List<BleNode> oldList, Date date, Integer minMachineCount) {
        if (CollectionUtil.isEmpty(oldList)) {
            return null;
        }
        /**机器编码，站点ID**/
        Map<String, Integer> map = getParkPointMapByCentralControl(oldList, date);
        /**站点ID，统计次数**/
        Map<Integer, Integer> countMap = new HashMap<>();
        for (BleNode o : oldList) {
            Integer parkPointId = map.get(o.getMachineNO());
            if (null != parkPointId) {
                //统计个数
                countMap.put(parkPointId, countMap.get(parkPointId) == null ? 1 : countMap.get(parkPointId) + 1);
            }
        }
        Integer max = 0;
        /**站点id**/
        Integer max_s = null;
        /**移除设备数量小于2的站点**/
        Iterator<Map.Entry<Integer, Integer>> iterator = countMap.entrySet()
                .iterator();
        while (iterator.hasNext()) {
            Map.Entry<Integer, Integer> next = iterator.next();
            Integer key = next.getKey();
            Integer value = next.getValue();
            if (value >= minMachineCount) {
                if (value > max) {
                    max = value;
                    max_s = key;
                }
            }
        }
        return max_s;
    }

    private HashMap<String, Integer> getParkPointMapByPhone(List<PhoneFuselocationData> oldList, Date date) {
        HashMap<String, Integer> map = new HashMap();
        List<String> machineNoList = new ArrayList();
        for (PhoneFuselocationData o : oldList) {
            if (DateTimeUtil.isInTime(o.getUpdateTime(), date, 20L) && null != o.getMachineNO()) {
                machineNoList.add(o.getMachineNO());
            }
        }
        List<ParkMachine> ParkMachineList = parkMachineService.getByMachineNOs(machineNoList);
        for (ParkMachine o : ParkMachineList) {
            map.put(o.getMachineNO(), o.getParkPointId());
        }
        return map;
    }

    private HashMap<String, Integer> getParkPointMapByCentralControl(List<BleNode> oldList, Date date) {
        HashMap<String, Integer> map = new HashMap();
        List<String> machineNoList = new ArrayList();
        for (BleNode o : oldList) {
            if (DateTimeUtil.isInTime(o.getUpdateTime(), date, 20L) && null != o.getMachineNO()) {
                machineNoList.add(o.getMachineNO());
            }
        }
        List<ParkMachine> ParkMachineList = parkMachineService.getByMachineNOs(machineNoList);
        for (ParkMachine o : ParkMachineList) {
            map.put(o.getMachineNO(), o.getParkPointId());
        }
        return map;
    }

    public Integer parkCheck(Integer blePointId, MachineStatus machineStatus, Integer parkPointId,
                             MachineBorrow machineBorrow, Machine machine) {
        if (blePointId != null) {
            ParkPoint parkPoint = parkPointService.getByParkPointId(blePointId);
            if (parkPoint != null) {
                double distance1 = GeoUtil
                        .getDistanceBy(machineStatus.getLon(), machineStatus.getLat(), parkPoint.getLo(),
                                parkPoint.getLa());
                if (distance1 >= 3000) {
                    blePointId = null;
                }
            }
        }
        /**兼容站点绑定道钉，检测*/
        return parkFeatureService
                .check(parkPointId, blePointId, machine.getMachineNO(), machine.getAccountId(),
                        machineBorrow.getOrderNO());
    }

    /**
     * 设备校验
     */
    public Machine machineCheck(Integer machineId, Point point, Map<String, Object> rideMap) {
        /**查询设备信息*/
        Machine machine = machineService.getByMachineId(machineId);
        if (machine == null) {
            throw new BaseException("-30005", "该编号不存在");
        }
        rideMap.put("adAccountId", machine.getAccountId());
        rideMap.put("machineNO", machine.getMachineNO());
        rideMap.put("userCode", machine.getUserCode());
        /**单车设备，手机位置必填*/
        if (MachineStateConstant.MACHINE_TYPE_BIKE.equals(machine.getMachineType())) {
            if (point == null) {
                throw new BaseException("-2001", "经度不能为空");
            }
            Assert.notNull(point.getX(), "-2001", "经度不能为空");
            Assert.notNull(point.getY(), "-2002", "纬度不能为空");
        }
        return machine;
    }

    /**
     * 头盔锁校验
     */
    @Override
    public Dispatch helmetCheck(Machine machine, Map<String, Object> rideMap,

                                MachineBorrow machineBorrow, Integer isLast) {
        Integer intelButton = 0;
        AccountConfigExt helmetExt = cacheService.getAccountConfigExtByKey(machine.getAccountId(),
                AccountConfigExtConstant.ACCOUNTCONFIG_INTEL);
        if (helmetExt != null) {
            intelButton = Integer.valueOf(helmetExt.getParamValue());
        }
        /**兼容用户未开启头盔锁，不判断头盔锁功能*/
        String helmet = redisService.get(RedisConstant.USER_OPEN_HELMET + machine.getMachineId());
        if (helmet != null || intelButton == 1) {
            /**头盔锁判断*/
            Dispatch dispatch1 = machineService

                    .checkHelmet(machine, machineBorrow.getUserId(), machineBorrow.getOrderNO(), rideMap, isLast);
            rideMap.put("helmet", -1);
            return dispatch1;
        }
        return null;
    }

    @Override
    public void photoUpload(MultipartFile multipartFile, User user) {
        InputStream inputStream = null;
        try {
            MachineBorrow machineBorrow = machineBorrowService.getByUserId(user.getUserId());
            if (machineBorrow == null) {
                throw new BaseException("-3053", "不存在订单数据");
            }
            Machine machine = machineService.getByMachineId(machineBorrow.getMachineId());

            File file = new File(SystemConstant.PHOTO_PATH + "/" + machineBorrow.getOrderNO() + ".jpg");
            inputStream = multipartFile.getInputStream();
            FileUtil.writeFromStream(inputStream, file);
            /**校验拍照结果*/
            String result = checkPhoto(machineBorrow.getOrderNO(), machine, user.getAccountId(), file);
            if (result == null) {
                return;
            }
            String code = JSON.parseObject(result)
                    .getString("code");
            redisService.add(RedisConstant.PHONE_PHOTO_RESULT + machineBorrow.getOrderNO(), result, 20);
            FileUtil.del(file);
            // 阿里云配置
            OssConfig ossConfig = cacheService.getOssConfig(user.getAccountId(), "gx-face");
            // 添加图片
            String imageId = UUID.randomUUID() + "." + code + ".png";
            byte[] bytes = multipartFile.getBytes();
            // 上传阿里云
            String fileName = OssFileUtils
                    .uploadByAliYun(new ByteArrayInputStream(bytes), ossConfig, imageId, multipartFile.getContentType(),
                            OssFileUtils.IMAGE);
            log.info("[拍照还车] 用户id={} 订单编号={} 图片地址={}", user.getUserId(), machineBorrow.getOrderNO(), fileName);
            PhotoUploadLog photoUploadLog = new PhotoUploadLog(null, machine.getAccountId(), user.getUserId(), user.getPhone(), null, ReturnTypeConstant.PHOTO, null, machine.getUserCode()
                    , machineBorrow.getOrderNO(), fileName, new Date(), getParkErrorCodeMsg(code));
            photoUploadLogDao.insert(photoUploadLog);

        } catch (Exception e) {
            log.error("上传异常", e);
            throw new BaseException("-101", "上传异常");
        } finally {
            try {
                inputStream.close();
            } catch (IOException e) {
                log.error(e.getMessage(), e);
            }
        }
    }

    private String getParkErrorCodeMsg(String code) {
        try {
            int codeInt = Integer.parseInt(code);
            for (TbitApiParkErrorCode errorCode : TbitApiParkErrorCode.values()) {
                if (errorCode.code == codeInt) {
                    return errorCode.msg;
                }
            }
            return "未知原因";
        } catch (Exception e) {
            return "未知原因";
        }
    }

    @Override
    public void photoOrQrCheck(Dispatch dispatch, Integer accountId, String orderNo, Integer isLast) {
        if (!Integer.valueOf(1)
                .equals(isLast)) {
            return;
        }

        List<Integer> outOfPointType = Lists.newArrayList(DisPatchConstant.DISPATCH_NOT_RUTURN, DisPatchConstant.DISPATCH_NO_IN_PARK);
        if (!outOfPointType.contains(dispatch.getType())) {
            return;
        }


        Long returnNum = redisService.getAtomicLong(RedisConstant.RETURN_NUM + orderNo);
        if (Objects.isNull(returnNum) || returnNum < 2) {
            return;
        }

        boolean openPhoto = AccountConfigExtConstant.ACCOUNTCONFIG_RETURN_BY_PHOTO_OPEN.equals(getConfig(AccountConfigExtConstant.ACCOUNTCONFIG_RETURN_BY_PHOTO, accountId));
        boolean openQr = AccountConfigExtConstant.ACCOUNTCONFIG_RETURN_BY_QR_OPEN.equals(getConfig(AccountConfigExtConstant.ACCOUNTCONFIG_RETURN_BY_QR, accountId));

        if (openPhoto && openQr) {
            dispatch.setType(DisPatchConstant.DISPATCH_PHOTO_OR_QR_RETURN);
//            outOfPointType.forEach(dispatch::removeType);
        } else if (openPhoto) {
            dispatch.setType(DisPatchConstant.DISPATCH_PHOTO_RETURN);
//            outOfPointType.forEach(dispatch::removeType);
        } else if (openQr) {
            dispatch.setType(DisPatchConstant.DISPATCH_QR_RETURN);
//            outOfPointType.forEach(dispatch::removeType);
        } else {
            log.info("AccountId {}  订单{} 未开启拍照还车或扫码还车功能", accountId, orderNo);
        }
    }

    @Override
    public boolean returnByPhotoCheck(User user, String orderNO, MultipartFile multipartFile, Point phonePoint) {


        MachineBorrow machineBorrow = machineBorrowService.getByUserId(user.getUserId());
        if (machineBorrow == null) {
            throw new BaseException("-3006", "已经还车，不存在借出记录");
        }

        Machine machine = Optional.ofNullable(machineService.getByMachineId(machineBorrow.getMachineId()))
                .orElseThrow(() -> new BaseException("车辆不存在:" + machineBorrow.getMachineId()));

        Integer accountId = machine.getAccountId();

        if (!AccountConfigExtConstant.ACCOUNTCONFIG_RETURN_BY_PHOTO_OPEN.equals(getConfig(AccountConfigExtConstant.ACCOUNTCONFIG_RETURN_BY_PHOTO, accountId))) {
            throw new BaseException("-3005", "未开启拍照还车功能");
        }
        String path = SystemConstant.PHOTO_PATH + "/license" + "/" + machineBorrow.getOrderNO() + ".jpg";
        File file = new File(path);
        InputStream inputStream = null;
        try {
            inputStream = multipartFile.getInputStream();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        FileUtil.writeFromStream(inputStream, file);

        JSONObject jsonObject = cacheService.getBrandJsonConfig(user.getAccountId(), BrandConstant.TBIT_CONFIG);
        boolean verify = false;
        if (!jsonObject.isEmpty()) {
            String appId = jsonObject.getString("appId");
            String nonce = jsonObject.getString("nonce");
            String appSecret = jsonObject.getString("appSecret");
            String resp = TbitHttpServiceUtil.licensePhotoReturnBike(appId, nonce, appSecret, FaceUtil.convert(file), null);
            if (StringUtils.isNotBlank(resp)) {
                JSONObject respObject = JSON.parseObject(resp);
                if ("200".equalsIgnoreCase(respObject.getString("code")) && Objects.nonNull(respObject.getJSONObject("data"))) {
                    Integer res = respObject.getJSONObject("data").getInteger("code");
                    verify = Objects.nonNull(res) && TbitHttpServiceUtil.LICENSE_PHOTO_RETURN_BIKE_SUCCESS.equals(res);
                }
            }
        }
        String fileName = null;
        try {
            // 阿里云配置
            OssConfig ossConfig = cacheService.getOssConfig(user.getAccountId(), "gx-face");
            // 添加图片
            String imageId = UUID.randomUUID() + (verify ? ".1" : ".0") + ".png";
            byte[] bytes = multipartFile.getBytes();
            // 上传阿里云
            fileName = OssFileUtils
                    .uploadByAliYun(new ByteArrayInputStream(bytes), ossConfig, imageId, multipartFile.getContentType(),
                            OssFileUtils.IMAGE);
            log.info("[车牌拍照还车] 用户id={} 订单编号={} 图片地址={}", user.getUserId(), machineBorrow.getOrderNO(), fileName);
        } catch (Exception e) {
            log.error("[车牌拍照还车] 照片上传异常", e);
        }
        LicensePhotoData licensePhotoData = new LicensePhotoData(machineBorrow.getOrderNO(), machine.getMachineNO(),
                machine.getAccountId(), fileName
                , DateUtil.formatDateTime(new Date()), verify);
        //位置校验，区域外不许使用
        Boolean result = checkPos(machine, phonePoint, licensePhotoData);
        terControlService.send(
                RabbitMQData.queueExchange, RabbitMQData.slskey, String.valueOf(GatewayProtocol.MQ_MSG_ID_SLS_LOG_LICENSE_PUSH),
                "", JSON.toJSONString(licensePhotoData));
        PhotoUploadLog photoUploadLog = new PhotoUploadLog(null, machine.getAccountId(), user.getUserId(), user.getPhone(), null, ReturnTypeConstant.PHOTO, verify && result ? 1 : 0, machine.getUserCode()
                , machineBorrow.getOrderNO(), fileName, new Date(), (result ? "车牌拍照还车" : "运营区域外"));
        photoUploadLogDao.insert(photoUploadLog);
        if (!result) {
            throw new BaseException("-3050", "运营区域外,请在运营区域内使用");
        }
        log.info("用户{}订单{}车牌拍照还车结果{}", user.getUserId(), orderNO, verify);

        return verify;
    }

    @Override
    public boolean returnByQrCheck(User user, String orderNO, String scanMachineNo, Point phonePoint) {

        MachineBorrow machineBorrow = machineBorrowService.getByUserId(user.getUserId());
        if (machineBorrow == null) {
            throw new BaseException("-3006", "已经还车，不存在借出记录");
        }

        Machine machine = Optional.ofNullable(machineService.getByMachineId(machineBorrow.getMachineId()))
                .orElseThrow(() -> new BaseException("车辆不存在:" + machineBorrow.getMachineId()));

        Integer accountId = machine.getAccountId();

        if (!AccountConfigExtConstant.ACCOUNTCONFIG_RETURN_BY_QR_OPEN.equals(getConfig(AccountConfigExtConstant.ACCOUNTCONFIG_RETURN_BY_QR, accountId))) {
            throw new BaseException("-3005", "未开启扫码还车功能");
        }
        Map<String, Object> queryMap = new HashMap<>();
        queryMap.put("accountId", accountId);
        queryMap.put("paramKey", AccountConfigExtConstant.RETURN_BY_QR_MAX_DISTANCE);
        AccountConfigExt maxDistanceBetweenScanQRBikeConfig = accountConfigExtDao.getByKey(queryMap);
        double maxDistanceBetweenScanQRBike = Optional.ofNullable(maxDistanceBetweenScanQRBikeConfig)
                .map(AccountConfigExt::getParamValue)
                .map(Double::valueOf)
                .orElse(DefaultMaxDistanceBetweenScanQRBike);

        Machine scanedMachine = Optional.ofNullable(machineService.getByUserCode(scanMachineNo))
                .orElseThrow(() -> new BaseException("扫码车辆不存在:" + scanMachineNo));

        Machine borrowMachine = machineService.getByMachineId(machineBorrow.getMachineId());
        if (Objects.isNull(borrowMachine)) {

            throw new BaseException("借出车辆不存在");
        }

        boolean canReturn = true;
        StringBuilder remark = new StringBuilder();
        remark.append(scanedMachine.getUserCode());
        try {
            // 校验：两车需在同一运营区
            if (!Objects.equals(machine.getAccountId(), scanedMachine.getAccountId())) {
                log.error("[拍二维码还车] 用户{}订单{}订单车辆区域{}和扫码车辆区域{}不一致", user.getUserId(), orderNO, machine.getAccountId(), scanedMachine.getAccountId());
                remark.append("两车不在同一运营区;");
                return canReturn = false;
            }

            double distance = machineService.calculateDistance(machineBorrow.getMachineId(), scanedMachine.getMachineId());

            remark.append("两车距离为").append(NumberUtil.roundStr(distance, 1)).append("米;");
            if (distance > maxDistanceBetweenScanQRBike) {
                log.error("[拍二维码还车] 用户{}订单{}扫码还车车辆{}距离借出车辆{}超过{}米", user.getUserId(), orderNO, scanedMachine.getMachineId(), machineBorrow.getMachineId(), maxDistanceBetweenScanQRBike);
                remark.append("超过配置值").append(maxDistanceBetweenScanQRBike).append("米;");
                return canReturn = false;
            }

            Integer parkPointId = parkMachineService.checkIfMachineInPark(borrowMachine.getAccountId(), scanMachineNo);
            if (parkPointId == null) {
                remark.append("扫码车辆不在站点;");
                return canReturn = false;
            }
            log.info("用户{}订单{}扫码还车车辆{}在站点{}", user.getUserId(), orderNO, scanMachineNo, parkPointId);
            //位置校验，区域外不许使用

            Boolean result = checkPos(machine, phonePoint, new LicensePhotoData());
            if (!result) {
                canReturn = false;
                remark.append("运营区域外;");
                throw new BaseException("-3050", "运营区域外,请在运营区域内使用");
            }
            return canReturn;
        } finally {
            PhotoUploadLog photoUploadLog = new PhotoUploadLog(null, accountId, user.getUserId(), user.getPhone(), null,
                    ReturnTypeConstant.QRCODE, canReturn ? 1 : 0,
                    machine.getUserCode(), machineBorrow.getOrderNO(), null,
                    new Date(), remark.toString());
            photoUploadLogDao.insert(photoUploadLog);
        }
    }

    /**
     * 申诉还车位置校验
     *
     * @param machine
     * @param point
     */
    private Boolean checkPos(Machine machine, Point point, LicensePhotoData licensePhotoData) {
        MachineStatus machineStatus = machineStatusService.getByMachineNO(machine.getMachineNO(), MapConstant.MAP_GOOGLE);
        if (machineStatus == null || machineStatus.getLon() == null || machineStatus.getLat() == null) {
            throw new BaseException("-3073", "车辆定位信息不存在");
        }
        if (point.getX() == null) {
            return true;
        }
        List<Geo> geoList = geoService.getByAccountId(machine.getAccountId());
        //车辆位置校验
        Point machinePoint = new Point(machineStatus.getLon(), machineStatus.getLat());
        boolean machineResult = isInGeo(geoList, machinePoint, machinePoint);
        //手机位置校验
        boolean phoneResult = isInGeo(geoList, point, point);
        licensePhotoData.setMachineLon(machinePoint.getX());
        licensePhotoData.setMachineLat(machinePoint.getY());
        licensePhotoData.setPhoneLon(point.getX());
        licensePhotoData.setPhoneLat(point.getY());
        licensePhotoData.setMachineOutArea(machineResult);
        licensePhotoData.setPhoneOutArea(phoneResult);
        if (!machineResult || !phoneResult) {
            return false;
        }
        return true;
    }

    private String getConfig(String key, Integer accountId) {
        AccountConfigExt accountConfigExt = cacheService.getAccountConfigExtByKey(accountId, key);
        log.info("accountId:{} key:{} accountConfigExt:{}", accountId, key, accountConfigExt);
        if (accountConfigExt == null) {
            return null;
        }
        return accountConfigExt.getParamValue();
    }

    /**
     * 校验拍照还车是否满足条件
     *
     * @param orderNO
     * @param machine
     * @param accountId
     * @return
     */
    public String checkPhoto(String orderNO, Machine machine, Integer accountId, File file) {
        /**区域是否打开开关*/
        AccountConfigExt configExt = cacheService.getAccountConfigExtByKey(machine.getAccountId(),
                AccountConfigExtConstant.ACCOUNTCONFIG_PHONE);
        if (configExt == null || "0".equalsIgnoreCase(configExt.getParamValue())) {
            return null;
        }

        //品牌是否配置还车方式
        String type = Optional.ofNullable(cacheService.getBrandRemoteConfig(accountId, BrandConstant.PHONE_RETURN_TYPE))
                .map(config -> config.getParamKV()
                        .substring(config.getParamKV()
                                .indexOf("=") + 1))
                .orElse("0");
        /**0代表不校验直接通过*/
        if ("0".equalsIgnoreCase(type)) {
            return "{\"msg\":\"停车成功\",\"code\":200}";
        }
        /**角度，误差距离配置*/
        AccountConfigExt accountConfigExt = cacheService
                .getAccountConfigExtByKey(machine.getAccountId(),
                        AccountConfigExtConstant.ACCOUNTCONFIG_PHOTORETURN_ERROR);
        if (accountConfigExt == null || !accountConfigExt.getParamValue()
                .contains(",")) {
            return null;
        }
        Integer photoReturnAngle = Integer.valueOf(accountConfigExt.getParamValue()
                .split(",")[0]);
        Integer photoReturnDistance = Integer.valueOf(accountConfigExt.getParamValue()
                .split(",")[1]);
        Map<String, Object> queryMap = new HashMap<>();
        queryMap.put("machineId", machine.getMachineId());
        queryMap.put("functionType", MachineStateConstant.PHOTO_RETURN_CAR);
        MachineFunction machineFunction = machineFunctionDao.getByMachineId(queryMap);
        if (machineFunction == null || Objects.isNull(machineFunction.getFunctionMode())
                || Objects.equals(machineFunction.getFunctionValue(), 0)) {
            return null;
        }
        String result;
        if (Objects.equals(machineFunction.getFunctionMode(), FunctionModelConstant.HUAHUI_PEDAL_WITH_COLOR_CARD)) {
            //华慧
            result = huaHuiPhotoReturnCar(accountId, machine.getMachineId(), photoReturnAngle, photoReturnDistance, file);
            log.info("订单号{}华慧拍照还车结果{}", orderNO, result);
        } else {
            //自研
            result = tbitPhotoReturnCar(accountId, machine, photoReturnAngle, photoReturnDistance, file, machineFunction);
            log.info("订单号{}自研拍照还车结果{}", orderNO, result);
        }
        return result;
    }


    private String tbitPhotoReturnCar(Integer accountId, Machine machine, Integer photoReturnAngle,
                                      Integer photoReturnDistance, File file, MachineFunction machineFunction) {
        Integer machineId = machine.getMachineId();
        JSONObject jsonObject = cacheService.getBrandJsonConfig(accountId, BrandConstant.TBIT_CONFIG);
        Integer functionMode = machineFunction.getFunctionMode();
        if (!jsonObject.isEmpty()) {
            ColorCode colorCode = colorCodeDao.getByMachineId(machineId);
            if (colorCode == null && Objects.equals(functionMode, FunctionModelConstant.SELF_PRODUCED_PEDAL_WITH_COLOR_CARD)) {
                //2 自研踏板带颜色码
                log.info("设备id[{}]拍照还车未配置颜色码", machineId);
                return null;
            }
            String appId = jsonObject.getString("appId");
            String nonce = jsonObject.getString("nonce");
            String appSecret = jsonObject.getString("appSecret");
            //车牌拍照还车,需要传递模型类型
            if (FunctionModelConstant.isLicensePlate(functionMode)) {
                JSONObject resultObject = new JSONObject();
                //返回空代表不满足条件
                String result = TbitHttpServiceUtil.licensePhotoReturnBike(appId, nonce, appSecret, FaceUtil.convert(file), TbitHttpServiceUtil.LICENSE_MODEL_AN_YI);
                if (result == null) {
                    resultObject.put("code", 500);
                    return resultObject.toJSONString();
                }
                //{"code":200,"msg":"操作成功","data":{"code":1,"angle":5.0}}
                //车牌校验 {"code":200,"msg":"操作成功","data":{"code":1,"id":"A62275,A62426,A62469","reason":"license-plate:4,anyi_ebike:2"}}
                if (StringUtils.isNotBlank(machine.getLicense()) && functionMode.equals(FunctionModelConstant.SELF_PRODUCED_MACHINE_TAIL_LICENSE_PLATE)) {
                    JSONObject object = JSON.parseObject(result);
                    //返回data为空，不满足条件
                    if (StringUtils.isBlank(object.getString("data"))) {
                        resultObject.put("code", 500);
                        return resultObject.toJSONString();
                    }
                    //车牌不匹配，不满足条件
                    String license = object.getJSONObject("data").getString("id");
                    List<String> list = Arrays.asList(license.split(","));
                    if (list.stream().noneMatch(l -> machine.getLicense().endsWith(l)) || license.isEmpty()) {
                        log.info("车牌不匹配{}", machine.getMachineNO());
                        resultObject.put("code", 500);
                        return resultObject.toJSONString();
                    }
                }
                //车牌拍照返回数据构造响应格式 将data里面的code提取到最外层
                JSONObject object = JSON.parseObject(result);
                JSONObject data = object.getJSONObject("data");
                if (data == null) {
                    //为空时直接返回code=500
                    resultObject.put("code", 500);
                } else {
                    Integer code = data.getInteger("code");
                    resultObject.put("code", code == 1 ? 200 : 500);
                }
                return resultObject.toJSONString();
            }
            //自研拍车牌(识别脚踏板停车线)跟自研拍踏板模型一样
            if (Objects.equals(functionMode, FunctionModelConstant.SELF_PRODUCED_MACHINE_PEDAL_PLATE)) {
                functionMode = FunctionModelConstant.SELF_PRODUCED_PEDAL;
            }
            return TbitHttpServiceUtil
                    .photographAngle(appId, nonce, appSecret, FaceUtil.convert(file), photoReturnAngle, photoReturnDistance,
                            Optional.ofNullable(colorCode)
                                    .map(ColorCode::getContent)
                                    .orElse("-1"),
                            functionMode);
        }
        return null;
    }

    private String huaHuiPhotoReturnCar(Integer accountId, Integer machineId, Integer photoReturnAngle,
                                        Integer photoReturnDistance, File file) {
        BrandRemoteConfig brandRemoteConfig = cacheService.getBrandRemoteConfig(accountId, BrandConstant.HUAHUI_API_APISECRET);
        if (brandRemoteConfig == null) {
            log.info("[{}]华慧拍照还车秘钥未配置", accountId);
            return null;
        }
        ColorCode colorCode = colorCodeDao.getByMachineId(machineId);
        if (colorCode == null) {
            log.info("设备id[{}]华慧拍照还车未配置颜色码", machineId);
            return null;
        }
        String apiSecret = brandRemoteConfig.getParamKV()
                .substring(brandRemoteConfig.getParamKV()
                        .indexOf("=") + 1);
        //图片存储本地地址
        return HuaHuiHttpUtils.recognition(apiSecret, photoReturnAngle, photoReturnDistance,
                colorCode.getContent(), file);
    }

    /**
     * 是否开启了蓝牙道钉
     */
    @Override
    public Boolean checkBle(Integer machineId) {
        Map<String, Object> param = new HashMap<>();
        param.put("machineId", machineId);
        param.put("functionType", MachineStateConstant.MACHINE_TYPE_BLE);
        /**兼容道钉功能*/
        MachineFunction machineFunction = machineFunctionDao.getByMachineId(param);
        /**开关打开*/
        if (machineFunction != null && machineFunction.getFunctionValue() == 1) {
            return true;
        }
        return false;
    }

    @Override
    public void returnCheck(String userCode) {
        Long startTime = System.currentTimeMillis();
        Machine machine = machineService.getByUserCode(userCode);
        if (machine == null) {
            throw new BaseException("-30005", "该编号不存在");
        }

        MachineBorrow machineBorrow = machineBorrowService.getByMachineId(machine.getMachineId());
        if (machineBorrow == null) {
            throw new BaseException("-3053", "不存在订单数据");
        }
        //5.0.0 增加触发还车后限制开头盔锁命令 10S
        redisService.add(String.format(RedisConstant.RETURN_LIMIT_HELMET, machineBorrow.getOrderNO()), "", 10);

        redisService.incr(RedisConstant.RETURN_NUM + machineBorrow.getOrderNO(), 600);

        redisService.del(RedisConstant.HELMET_COMMAND_LOCK + machineBorrow.getOrderNO());
        boolean isStatus = false;
        Boolean sendPos = true;
        String param = "";
        /**自行车需要先发送查询终端状态*/
        if (MachineStateConstant.MACHINE_TYPE_BIKE.equals(machine.getMachineType())) {
            String serNO = UUID.randomUUID()
                    .toString();
            terControlService
                    .sendControl(serNO, machine.getMachineNO(), ControlTypeConstant.CONTROL_TYPE_GET,
                            ControlTypeConstant.CONTROL_TYPE_DEVICESTATUS, "", machine, false, -1,
                            machineBorrow.getUserId(), "还车获取车辆状态信息");
            redisService.add(FunctionConstant.CARSTATUS + machine.getMachineNO(), serNO, 60);
        } else {
            /**获取设备支持的所有功能*/
            List<MachineFunction> functions = machineFunctionDao.getAll(machine.getMachineId());
            if (functions.size() > 0) {
                if (!buildNewReturnOrder(machine, functions, machineBorrow.getUserId())) {
                    sendPos = buildParam(functions, param, machine, machineBorrow, startTime);
                }
                /**预置-2，代表指令已发送未回复*/
                redisService.add(FunctionConstant.VERTICAL + machine.getMachineNO(), "-2", 60);

                redisService.add(RedisConstant.LOG_SIGN + machine.getMachineNO(), "-1", 20);
            }
            if (sendPos) {
                String serNO = UUID.randomUUID()
                        .toString();
                terControlService
                        .sendControl(serNO, machine.getMachineNO(), ControlTypeConstant.CONTROL_TYPE_CONTROL,
                                ControlTypeConstant.CONTROL_TYPE_LOCATION, "", machine, true, 1,
                                machineBorrow.getUserId(), "");
            }
        }
    }

    /**
     * 构造预还车指令
     *
     * @param machine   设备信息
     * @param functions 支持功能列表
     * @param userId    用户id
     * @return
     */
    public Boolean buildNewReturnOrder(Machine machine, List<MachineFunction> functions, Integer userId) {
        /**校验开关*/
        AccountConfigExt accountConfigExt = cacheService.getAccountConfigExtByKey(machine.getAccountId(), AccountConfigExtConstant.ACCOUNTCONFIG_NEW_ORDER);
        if (accountConfigExt != null && "0".equals(accountConfigExt.getParamValue())) {
            return false;
        }
        TerInfo terInfo = terInfoDao.getByMachineNO(machine.getMachineNO());
        if (terInfo == null) {
            return false;
        }
        boolean isXiaoAn = false;
        if ("XIAOAN".equalsIgnoreCase(terInfo.getFactoryCode())) {
            isXiaoAn = true;
        }
        /**扩展参数bit20为1才支持预还车指令*/
        TerAttrEx terAttrEx = terAttrExService.getByMachineNO(machine.getMachineNO(),
                TerAttrExConstant.ATTREX_REVERSE);
        if ((terAttrEx == null || !StringUtil.getIndex(terAttrEx.getValue(), 20)) && !isXiaoAn) {
            return false;
        }
        /**软件版本号具体协议定义参考终端协议*/
        String version = terInfo.getSoftVersion();
        if ((!version.contains("H-") || !version.contains("S-")) && !isXiaoAn) {
            return false;
        }
        Long lockConditions = 0L;
        String hardCode = StringUtil.getHardCode(version, "H-");
        String soft = StringUtil.getHardCode(version, "S-");
        /**功能支持*/
        for (MachineFunction function : functions) {
            switch (function.getFunctionType()) {
                case MachineStateConstant.MACHINE_TYPE_HELMAT:
                    /**通用头盔锁*/
                    if (isXiaoAn || StringUtil.getIndex(hardCode, 4)) {
                        lockConditions = buildHelmetTlv(lockConditions, soft, isXiaoAn);
                    } else {
                        log.warn("设备编号终端版本不支持头盔锁" + machine.getMachineNO());
                    }
                    break;
                case MachineStateConstant.MACHINE_TYPE_VERTICAL:
                    /**垂直停车*/
                    if (isXiaoAn || StringUtil.getIndex(hardCode, 10)) {
                        if (isXiaoAn || StringUtil.getIndex(soft, 13)) {
                            lockConditions = ObjectUtil.buildTlv(lockConditions, 7);
                        }
                    } else {
                        log.warn("设备编号终端版本不支持垂直停车" + machine.getMachineNO());
                    }
                    break;
                case MachineStateConstant.MACHINE_TYPE_HIGH:
                    /**高精度定位*/

                    break;
                case MachineStateConstant.MACHINE_TYPE_JC:
                    /**脚撑*/

                    break;
                case MachineStateConstant.MACHINE_TYPE_BLE:
                    /**道钉*/
                    if (isXiaoAn || StringUtil.getIndex(hardCode, 6)) {
                        if (isXiaoAn || StringUtil.getIndex(soft, 5)) {
                            lockConditions = ObjectUtil.buildTlv(lockConditions, 6);
                        }
                    } else {
                        log.warn("设备编号终端版本不支持道钉" + machine.getMachineNO());
                    }
                    break;
                case MachineStateConstant.MACHINE_TYPE_RFID:
                    /**RFID*/
                    if (isXiaoAn || StringUtil.getIndex(hardCode, 5)) {
                        if (isXiaoAn || StringUtil.getIndex(soft, 4)) {
                            lockConditions = ObjectUtil.buildTlv(lockConditions, 5);
                        }
                    } else {
                        log.warn("设备编号终端版本不支持RFID" + machine.getMachineNO());
                    }
                    break;
                case MachineStateConstant.MACHINE_TYPE_WEBCAM:
                    /**摄像头*/
                    lockConditions = buildSxtTlv(lockConditions, soft, hardCode, machine.getMachineNO(), isXiaoAn);
                    break;
                case MachineStateConstant.MACHINE_TYPE_T_SXT:
                    /**T摄像头*/
                    lockConditions = buildSxtTlv(lockConditions, soft, hardCode, machine.getMachineNO(), isXiaoAn);
                    break;
                case MachineStateConstant.MACHINE_TYPE_INTEL_HELMET:
                    /**智能头盔锁*/
                    if (isXiaoAn || StringUtil.getIndex(hardCode, 8)) {
                        lockConditions = buildHelmetTlv(lockConditions, soft, isXiaoAn);
                    } else {
                        log.warn("终端版本不支持智能头盔锁" + machine.getMachineNO());
                    }
                    break;
                case MachineStateConstant.MACHINE_TYPE_CDZ:
                    /**充电桩*/
                    if (isXiaoAn || StringUtil.getIndex(hardCode, 5)) {
                        if (isXiaoAn || StringUtil.getIndex(soft, 4)) {
                            lockConditions = ObjectUtil.buildTlv(lockConditions, 5);
                        }
                    } else {
                        log.warn("设备编号终端版本不支持RFID" + machine.getMachineNO());
                    }
                    break;
                default:
                    break;
            }
        }
        /**预还车指令构造value*/
        ReturnBikeData returnBikeData = new ReturnBikeData();
        returnBikeData.setLockConditions(lockConditions);
        returnBikeData.setCtrl(1L);
        returnBikeData.setReserved2(0L);
        returnBikeData.setReserved3(0L);
        returnBikeData.setReserved4(0L);
        String serNO5 = UUID.randomUUID()
                .toString();
        log.info("预还车指令发送returnBikeData={}, machine={}", JSON.toJSONString(returnBikeData), JSON.toJSONString(machine));
        terControlService.sendControl(serNO5, machine.getMachineNO(),
                ControlTypeConstant.CONTROL_TYPE_CONTRO_TLV, ControlTypeConstant.CONTROL_RETURNBIKE,
                JSON.toJSONString(returnBikeData), machine, false, -1, userId, "预还车指令");
        return true;
    }

    /**
     * 构造摄像头指令详情
     *
     * @param lockConditions
     * @param soft
     * @param hardCode
     * @param machineNO
     * @return
     */
    public Long buildSxtTlv(long lockConditions, String soft, String hardCode, String machineNO, boolean isXiaoAn) {
        String webcamNum = redisService
                .get(RedisConstant.WEBCAM_FAIL_KEY_PREFIX + machineNO);
        if (webcamNum == null) {
            webcamNum = "0";
        }
        /**缓存还车角度判断次数*/
        redisService.add(RedisConstant.WEBCAM_FAIL_KEY_PREFIX + machineNO,
                String.valueOf(Integer.valueOf(webcamNum) + 1), 60);
        if (isXiaoAn || StringUtil.getIndex(hardCode, 7)) {
            if (isXiaoAn || StringUtil.getIndex(soft, 7)) {
                lockConditions = ObjectUtil.buildTlv(lockConditions, 4);
            }
        } else {
            log.warn("终端版本不支持摄像头" + machineNO);
        }
        return lockConditions;
    }

    /**
     * 构造头盔锁详细
     *
     * @param lockConditions
     * @param soft
     * @return
     */
    public Long buildHelmetTlv(long lockConditions, String soft, boolean isXiaoAn) {
        if (isXiaoAn || StringUtil.getIndex(soft, 0)) {
            lockConditions = ObjectUtil.buildTlv(lockConditions, 0);
        }
        if (isXiaoAn || StringUtil.getIndex(soft, 1)) {
            lockConditions = ObjectUtil.buildTlv(lockConditions, 1);
        }
        if (isXiaoAn || StringUtil.getIndex(soft, 2)) {
            lockConditions = ObjectUtil.buildTlv(lockConditions, 2);
        }
        if (isXiaoAn || StringUtil.getIndex(soft, 3)) {
            lockConditions = ObjectUtil.buildTlv(lockConditions, 3);
        }
        return lockConditions;
    }

    /**
     * 构造指令发送
     *
     * @param functions
     * @param param
     * @param machine
     * @param machineBorrow
     * @param startTime
     */
    public Boolean buildParam(List<MachineFunction> functions, String param, Machine machine
            , MachineBorrow machineBorrow, Long startTime) {
        boolean isStatus = false;
        Boolean sendPos = true;
        for (MachineFunction fun : functions) {
            switch (fun.getFunctionType()) {
                case MachineStateConstant.MACHINE_TYPE_HELMAT:
                    if (!isStatus) {
                        /**头盔锁*/
                        param = param + ControlTypeConstant.CONTROL_TYPE_DEVICESTATUS + ControlTypeConstant.CONTROL_TYPE_DEVICESTATUSEX;
                        isStatus = true;
                    }
                    break;
                case MachineStateConstant.MACHINE_TYPE_VERTICAL:
                    /**垂直停车*/
                    param = param + ControlTypeConstant.CONTROL_TYPE_VEHHEADINGANGLE;

                    sendPos = false;
                    break;
                case MachineStateConstant.MACHINE_TYPE_HIGH:
                    /**高精度定位*/
                    sendPos = false;
                    break;
                case MachineStateConstant.MACHINE_TYPE_JC:
                    /**脚撑*/
                    String serNO4 = UUID.randomUUID()
                            .toString();
                    /**暂时先用rfid*/
                    terControlService.sendControl(serNO4, machine.getMachineNO(),
                            ControlTypeConstant.CONTROL_TYPE_CONTROL,
                            ControlTypeConstant.CONTROL_RFIDREADSTATUS, "", machine, false, -1,
                            machineBorrow.getUserId(), "脚撑状态查询");
                    /**数据收集*/
                    addOrderLog(machine, serNO4, OrderLogTypeConstant.CONTROL_RFID);
                    sendPos = false;
                    break;
                case MachineStateConstant.MACHINE_TYPE_CDZ:
                    /**脚撑*/
                    String cdzSerNO = UUID.randomUUID()
                            .toString();
                    /**暂时先用rfid*/
                    terControlService.sendControl(cdzSerNO, machine.getMachineNO(),
                            ControlTypeConstant.CONTROL_TYPE_CONTROL,
                            ControlTypeConstant.CONTROL_RFIDREADSTATUS, "", machine, false, -1,
                            machineBorrow.getUserId(), "充电桩状态查询");
                    /**数据收集*/
                    addOrderLog(machine, cdzSerNO, OrderLogTypeConstant.CONTROL_RFID);
                    sendPos = false;
                    break;
                case MachineStateConstant.MACHINE_TYPE_BLE:
                    String serNO1 = UUID.randomUUID()
                            .toString();
                    /**道钉*/
                    terControlService.sendControl(serNO1, machine.getMachineNO(),
                            ControlTypeConstant.CONTROL_TYPE_CONTROL, ControlTypeConstant.CONTROL_POINT, "",
                            machine, false, -1, machineBorrow.getUserId(), "立即上传融合定位包");
                    /**添加设备下发融合定位包标志*/
                    redisService.add(RedisConstant.RIDELOG_RETURN + machine.getMachineNO(),
                            machineBorrow.getOrderNO(), 11);
                    /**清除之前设备上传的道钉信息*/
                    //redisService.del(RedisConstant.MACHINE_NO_BLEINFO + machine.getMachineNO());
                    /**预指令发送时间*/
                    if (SystemData.orderTime.get(machineBorrow.getOrderNO()) == null) {
                        SystemData.orderTime.put(machineBorrow.getOrderNO(), String.valueOf(startTime));
                    }
                    sendPos = false;
                    break;
                case MachineStateConstant.MACHINE_TYPE_RFID:
                    String serNO3 = UUID.randomUUID()
                            .toString();

                    /**rfid*/
                    terControlService.sendControl(serNO3, machine.getMachineNO(),
                            ControlTypeConstant.CONTROL_TYPE_CONTROL,
                            ControlTypeConstant.CONTROL_RFIDREADSTATUS, "", machine, false, -1,
                            machineBorrow.getUserId(), "RFID状态查询");

                    /**rfid数据收集*/
                    addOrderLog(machine, serNO3, OrderLogTypeConstant.CONTROL_RFID);
                    sendPos = false;
                    break;
                case MachineStateConstant.MACHINE_TYPE_WEBCAM:
                    String serNO2 = UUID.randomUUID()
                            .toString();
                    /**摄像头*/
                    terControlService.sendControl(serNO2, machine.getMachineNO(),
                            ControlTypeConstant.CONTROL_TYPE_CONTROL, ControlTypeConstant.CONTROL_AICAMERARET,
                            "", machine, false, -1, machineBorrow.getUserId(), "摄像头状态查询");
                    addOrderLog(machine, serNO2, OrderLogTypeConstant.CONTROL_WEBCAM);
                    sendPos = false;
                    break;
                case MachineStateConstant.MACHINE_TYPE_T_SXT:
                    String serNO5 = UUID.randomUUID()
                            .toString();
                    /**摄像头*/
                    terControlService.sendControl(serNO5, machine.getMachineNO(),
                            ControlTypeConstant.CONTROL_TYPE_CONTROL, ControlTypeConstant.CONTROL_AICAMERARET,
                            "", machine, false, -1, machineBorrow.getUserId(), "T摄像头状态查询");
                    addOrderLog(machine, serNO5, OrderLogTypeConstant.CONTROL_WEBCAM);
                    sendPos = false;
                    break;
                case MachineStateConstant.MACHINE_TYPE_RGPS:
                    //RGPS
                    param = param + ControlTypeConstant.CONTROL_TYPE_RGPS;
                    sendPos = false;
                    break;
                case MachineStateConstant.MACHINE_TYPE_INTEL_HELMET:
                    if (!isStatus) {
                        /**头盔锁*/
                        param = param + ControlTypeConstant.CONTROL_TYPE_DEVICESTATUS + ControlTypeConstant.CONTROL_TYPE_DEVICESTATUSEX;
                        isStatus = true;
                    }
                    break;

                default:
                    break;
            }

        }
        if (!"".equals(param)) {
            String serNO = UUID.randomUUID()
                    .toString();
            terControlService
                    .sendControl(serNO, machine.getMachineNO(), ControlTypeConstant.CONTROL_TYPE_GET,
                            param, "", machine, false, -1, machineBorrow.getUserId(), "预还车信息获取");
            sendPos = false;
        }
        return sendPos;
    }


    /**
     * 手机道钉检测
     */
    @Override
    public List<String> phoneBleCheck(Map<String, BleNode> map, String machineNO, Integer accountId) {
        Machine machine = machineService.getByMachineNO(machineNO);
        /**手机道钉信号校验*/
        String bleNO = redisService.get(RedisConstant.PHONE_UPLOAD_BLE + machine.getUserCode());
        /**清除缓存数据*/
        SystemData.bleNodeMap.remove(machineNO);
        Integer level = getLevel(accountId);
        if (bleNO != null) {
            List<BleNode> list = JSON.parseArray(bleNO, BleNode.class);
            List<String> no = new ArrayList<>();
            for (BleNode s : list) {
                if (s.getBleType() == null) {
                    s.setBleType(BleConstant.BLE_JT);
                }
                try {
                    if (s.getBleType()
                            .equals(BleConstant.BLE_JT)) {
                        /**道钉信号字节转ascii*/
                        s.setBleTID(ByteUtil.BytesToHexString(s.getBleTID()
                                .getBytes("ascii")));
                    }
                    if (level == null || level == 0 || level >= Math.abs(Integer.valueOf(s.getBleLevel()))) {
                        no.add(s.getBleTID());
                        map.put(s.getBleTID(), s);
                    }
                } catch (UnsupportedEncodingException e) {
                    log.error(e.getMessage(), e);
                }
            }
            return no;
        }
        return new ArrayList<>();
    }

    /**
     * 终端蓝牙检测
     */
    @Override
    public Integer machineBleCheck(String machineNO, Integer accountId, String orderNO) {
        Integer parkPointId = null;
        String tmp = redisService.get(RedisConstant.MACHINE_NO_BLEINFO + machineNO);
        if (tmp != null) {
            List<BleInfo> list = JSON.parseArray(tmp, BleInfo.class);
            if (list != null && list.size() > 0) {
                for (BleInfo bleInfo : list) {
                    List<BleNode> ddnodes = bleInfo.getNodes();
                    if (ddnodes.size() > 0) {
                        Integer level = getLevel(accountId);
                        for (BleNode b : ddnodes) {
                            Integer bleLevel = level;

                            Map<String, Object> map = new HashMap<>();
                            map.put("machineNO", b.getBleTID());
                            map.put("adAccountId", accountId);
                            BleBeacon bleBeacon = bleBeaconDao.getByNO(map);
                            ParkFeature parkFeature;
                            //String mac = "mac=" + b.getBleMAC() + ";rssi=-100";
                            String mac = "mac=" + b.getBleMAC() + ";rssi=" + b.getBleLevel();
                            /**还车道钉信息缓存*/
                            redisService.add(RedisConstant.MACHINE_BLE + machineNO, mac, 7 * 24 * 3600);
                            if (bleBeacon != null && ((System.currentTimeMillis() - bleInfo.getUpdateTime())
                                    <= 20000)) {
                                parkPointId = bleBeacon.getParkPointId();
                                /**对站点是否开启道钉rssi值进行判断*/
                                parkFeature = parkFeatureService.getByParkPointId(parkPointId,
                                        ParkFeatureConstant.PARKFEATURE_TYPE_BLE);
                                if (parkFeature != null && parkFeature.getEnable() == 1) {
                                    /**rssi值设定格式为rssi=70,站点设置了rssi值以站点为主*/

                                    if (parkFeature.getPropert() != null && parkFeature.getPropert()
                                            .contains("rssi")
                                            && parkFeature.getPropert()
                                            .length() >= 6) {
                                        bleLevel = Integer.valueOf(parkFeature.getPropert()
                                                .substring(parkFeature.getPropert()
                                                        .indexOf("=") + 1));
                                    }
                                }
                                if (bleLevel == null || bleLevel == 0 || bleLevel >= Math
                                        .abs(Integer.valueOf(b.getBleLevel()))) {
                                    log.info(orderNO + "判断道钉等级=" + bleLevel + "," + b + ",站点id=" + parkPointId);
                                    break;
                                } else {
                                    parkPointId = null;
                                }

                            }

                        }
                    }
                }
            }
        } else {
            log.info("设备编号=" + machineNO + "未检测到道钉信息");
        }
        return parkPointId;
    }

    /**
     * 获取道钉rssi等级
     */
    public Integer getLevel(Integer accountId) {
        Map<String, Object> map1 = new HashMap<>();
        map1.put("accountId", accountId);
        map1.put("paramKey", AccountConfigExtConstant.ACCOUNTCONFIG_BLE_RSSI);
        AccountConfigExt accountConfigExt = accountConfigExtDao.getByKey(map1);
        Integer levle = null;
        if (accountConfigExt != null) {
            levle = Integer.valueOf(accountConfigExt.getParamValue());
        }
        return levle;
    }


    /**
     * 还车位置入库
     *
     * @param point         经纬度点
     * @param machine       设备信息
     * @param machineBorrow 借车信息
     * @param from          点来源
     */
    void addReturnLog(Point point, Machine machine, MachineBorrow machineBorrow, Integer from) {
        Map<String, Object> map = new HashMap<>();
        map.put("orderNO", machineBorrow.getOrderNO());
        map.put("posFrom", from);
        ReturnPosLog returnPosLogDB = returnPosLogDao.getByOrderNO(map);
        /**还车记录还车位置*/
        ReturnPosLog returnPosLog = new ReturnPosLog();
        returnPosLog.setLon(point.getX());
        returnPosLog.setLat(point.getY());
        returnPosLog.setMachineNO(machine == null ? "" : machine.getMachineNO());
        returnPosLog.setUserCode(machine == null ? "" : machine.getUserCode());
        returnPosLog.setOrderNO(machineBorrow.getOrderNO());
        returnPosLog.setPosFrom(from);
        returnPosLog.setPosTime(new Date());
        if (returnPosLogDB != null) {
            returnPosLogDao.edit(returnPosLog);
        } else {
            returnPosLogDao.add(returnPosLog);
        }

    }

    /**
     * 查询是否在区域
     */
    public Dispatch isInGeo(List<Geo> geos, Integer accountId, Point checkPoint, Integer machineId, Boolean isBan) {

        Dispatch dispatch = new Dispatch(DisPatchConstant.DISPATCH_PARK, null);
        Geo geo = geos.get(0);
        if (geo.getGeoSwitch() == 1) {
            AdAccountFee adAccountFee = adAccountFeeService.getByAccountId(accountId, machineId);
            if (adAccountFee == null) {
                //不支持调度，不在还车点内
                dispatch = new Dispatch(DisPatchConstant.DISPATCH_NOT_RUTURN, null);
            } else {
                /**判断是否在运营区域内*/
                List<Point> points = GeoUtil.getPoints(geos.get(0)
                        .getPoints());
                Boolean isInArea = GeoUtil.IsPtInPoly(checkPoint, points);
                /**是否打开开关*/
                if (adAccountFee.getDispatchSwitch() == 1) {
                    /**经纬度点在区域内*/
                    if (isInArea) {
                        return dispatch;
                    } else {
                        if (adAccountFee.getAreaMoney() > 0) {
                            return new Dispatch(DisPatchConstant.DISPATCH_NOT_IN_AREA,
                                    (double) adAccountFee.getAreaMoney());
                        } else if (adAccountFee.getAreaMoney() == 0 && isBan) {
                            return new Dispatch(DisPatchConstant.DISPATCH_NOT_RUTURN_IS0, null);
                        }
                    }
                }
            }
        } else {
            return new Dispatch(DisPatchConstant.DISPATCH_NOT_RUTURN, null);
        }
        return dispatch;
    }

    /**
     * 是否在区域
     */
    public boolean isInArea(List<Geo> geos, Integer accountId, Point checkPoint,
                            Integer machineId) {
        Geo geo = geos.get(0);
        if (geo.getGeoSwitch() == 1) {
            /**是否有配置调度费*/
            AdAccountFee adAccountFee = adAccountFeeService.getByAccountId(accountId, machineId);
            if (adAccountFee == null) {
                return false;
            } else {
                /**是否打开开关*/
                if (adAccountFee.getDispatchSwitch() == 1) {

                    /**判断是否在运营区域内*/
                    List<Point> points = GeoUtil.getPoints(geos.get(0)
                            .getPoints());
                    return GeoUtil.IsPtInPoly(checkPoint, points);
                }
            }
        }
        return false;
    }

    //是否在区域内
    public boolean isInGeo(List<Geo> geos, Point mnoPoint, Point checkPoint) {
        /**车辆位置不为空，以车辆位置为主判断是否超区*/
        if (mnoPoint != null) {
            checkPoint = mnoPoint;
        }
        Geo geo = geos.get(0);
        List<Point> points = GeoUtil.getPoints(geo
                .getPoints());
        return GeoUtil.IsPtInPoly(checkPoint, points);
    }

    public Dispatch isInAreaOld(List<Geo> geos, Integer accountId, Point mnoPoint, Point checkPoint,
                                Integer machineId) {
        Dispatch dispatch;
        Geo geo = geos.get(0);
        if (geo.getGeoSwitch() == 1) {
            /**是否有配置调度费*/
            AdAccountFee adAccountFee = adAccountFeeService.getByAccountId(accountId, machineId);
            if (adAccountFee == null) {
                dispatch = new Dispatch(DisPatchConstant.DISPATCH_NOT_RUTURN, null);
            } else {
                /**是否打开开关*/
                if (adAccountFee.getDispatchSwitch() == 1) {
                    /**车辆位置不为空，以车辆位置为主判断是否超区*/
                    if (mnoPoint != null) {
                        checkPoint = mnoPoint;
                    }

                    /**判断是否在运营区域内*/
                    List<Point> points = GeoUtil.getPoints(geos.get(0)
                            .getPoints());
                    Boolean isInArea = GeoUtil.IsPtInPoly(checkPoint, points);
                    /**经纬度点在区域内*/
                    if (isInArea) {
                        if (adAccountFee.getParkPointMoney() > 0) {
                            //支持调度，不在还车点
                            dispatch = new Dispatch(DisPatchConstant.DISPATCH_NO_IN_PARK,
                                    (double) adAccountFee.getParkPointMoney());
                        } else {
                            dispatch = new Dispatch(DisPatchConstant.DISPATCH_NOT_RUTURN, null);
                        }
                    } else {
                        if (adAccountFee.getAreaMoney() > 0) {
                            dispatch = new Dispatch(DisPatchConstant.DISPATCH_NOT_IN_AREA,
                                    (double) adAccountFee.getAreaMoney());
                        } else {
                            dispatch = new Dispatch(DisPatchConstant.DISPATCH_NOT_RUTURN, null);
                        }
                    }
                } else {
                    dispatch = new Dispatch(DisPatchConstant.DISPATCH_NOT_RUTURN, null);
                }
            }
        } else {
            dispatch = new Dispatch(DisPatchConstant.DISPATCH_NOT_RUTURN, null);
        }
        return dispatch;
    }

    /**
     * 新增数据收集日志
     */
    public void addOrderLog(Machine machine, String serNO3, Integer type) {
        OrderReturn orderReturn = new OrderReturn();
        orderReturn.setMachineNO(machine.getMachineNO());
        orderReturn.setAccountId(machine.getAccountId());
        orderReturn.setOrderNO(serNO3);
        orderReturn.setAddTime(new Date());
        orderReturn.setType(type);
        orderReturnDao.add(orderReturn);
    }

    /**
     * @description: 学习规范免调度费，添加调度费到缓存
     * @author: mo.shanyong
     * @date: 2025/2/28 14:30
     * @param: orderNO 订单号
     * @param: reduceMoney 调度金额
     * @return: void
     */
    @Override
    public void addReduceDispatchMoney(String orderNO, Integer reduceMoney) {
        /**添加豁免标识*/
        redisService
                .add(RedisConstant.RIDELOG_REDUCE + orderNO, String.valueOf(reduceMoney),
                        60);
    }
}
